You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
624 lines
16 KiB
624 lines
16 KiB
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <windows.h>
|
|
#include <windef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
|
|
#include "dfsgeneric.hxx"
|
|
#include "dfsinit.hxx"
|
|
#include "dsgetdc.h"
|
|
#include "lm.h"
|
|
#include <dsrole.h>
|
|
#include <DfsReferralData.h>
|
|
#include <DfsReferral.hxx>
|
|
#include <dfsheader.h>
|
|
#include <Dfsumr.h>
|
|
#include <winsock2.h>
|
|
#include <DfsSiteCostCache.hxx>
|
|
#include <DfsSite.hxx>
|
|
|
|
#include "DfsSiteCostCache.tmh"
|
|
|
|
//
|
|
// Create a DFS_SITE_COST_DATA structure that is ready to
|
|
// get inserted in to the SiteCost Cache.
|
|
//
|
|
DFSSTATUS
|
|
DfsSiteCostCache::CreateCostData (
|
|
DfsSite *pDestinationSite,
|
|
ULONG Cost,
|
|
DFSSTATUS ValidityStatus,
|
|
PDFS_SITE_COST_DATA *ppNewData)
|
|
{
|
|
DFSSTATUS Status = ERROR_SUCCESS;
|
|
PDFS_SITE_COST_DATA pData = NULL;
|
|
|
|
pData = (PDFS_SITE_COST_DATA) DfsAllocateHashData( sizeof( DFS_SITE_COST_DATA ));
|
|
|
|
if (pData != NULL)
|
|
{
|
|
RtlZeroMemory(pData, sizeof(DFS_SITE_COST_DATA));
|
|
pData->Header.RefCount = 1;
|
|
pData->Header.pData = (PVOID)pData;
|
|
pData->ValidityStatus = ValidityStatus;
|
|
pData->Cost = Cost;
|
|
pData->AccessTime = GetTickCount();
|
|
|
|
//
|
|
// Key is the referenced pointer to the destination site itself.
|
|
//
|
|
pDestinationSite->AcquireReference();
|
|
pData->pDestinationSite = pDestinationSite;
|
|
pData->Header.pvKey = (PVOID)pData->pDestinationSite;
|
|
}
|
|
else
|
|
{
|
|
Status = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
*ppNewData = pData;
|
|
|
|
return Status;
|
|
}
|
|
|
|
DFSSTATUS
|
|
DfsSiteCostCache::Initialize(void)
|
|
{
|
|
DFSSTATUS Status = ERROR_SUCCESS;
|
|
NTSTATUS NtStatus = STATUS_SUCCESS;
|
|
SHASH_FUNCTABLE FunctionTable;
|
|
|
|
ZeroMemory(&FunctionTable, sizeof(FunctionTable));
|
|
|
|
FunctionTable.NumBuckets = DFS_DEFAULT_SITE_COST_NUM_BUCKETS;
|
|
FunctionTable.CompareFunc = DfsCompareDfsSites;
|
|
FunctionTable.HashFunc = DfsHashDfsSite;
|
|
FunctionTable.AllocFunc = DfsAllocateHashData;
|
|
FunctionTable.FreeFunc = DfsDeallocateHashData;
|
|
FunctionTable.AllocHashEntryFunc = DfsAllocateHashData;
|
|
FunctionTable.FreeHashEntryFunc = DfsSiteCostCache::DfsDeallocateSiteCostData;
|
|
|
|
NtStatus = ShashInitHashTable(&_pSiteCostTable, &FunctionTable);
|
|
Status = RtlNtStatusToDosError(NtStatus);
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
// Just delete the cache data entry.
|
|
VOID
|
|
DfsSiteCostCache::DfsDeallocateSiteCostData(PVOID pPointer )
|
|
{
|
|
PDFS_SITE_COST_DATA pSiteStructure = (PDFS_SITE_COST_DATA)pPointer;
|
|
|
|
if (pSiteStructure)
|
|
{
|
|
if (pSiteStructure->pDestinationSite != NULL)
|
|
{
|
|
pSiteStructure->pDestinationSite->ReleaseReference();
|
|
}
|
|
delete [] (PBYTE)pSiteStructure;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Create the hash entry and insert it in the hash table.
|
|
//
|
|
DFSSTATUS
|
|
DfsSiteCostCache::SetCost(
|
|
DfsSite *pDestinationSite,
|
|
ULONG Cost,
|
|
DWORD ValidityStatus)
|
|
{
|
|
DFSSTATUS Status = ERROR_SUCCESS;
|
|
PDFS_SITE_COST_DATA pCostData = NULL;
|
|
|
|
Status = CreateCostData( pDestinationSite,
|
|
Cost,
|
|
ValidityStatus,
|
|
&pCostData );
|
|
|
|
if (Status != ERROR_SUCCESS)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
Status = SetCostData( pDestinationSite,
|
|
pCostData );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// GetCost
|
|
//
|
|
// Given a target site, return it's cost. We know that the Source and the Destination
|
|
// sites are NOT the same at this point.
|
|
//
|
|
// This returns ERROR_SUCCESS if it finds a valid cost.
|
|
// ERROR_NOT_FOUND if the caller needs to GenerateCostMatrix.
|
|
//
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
DFSSTATUS
|
|
DfsSiteCostCache::GetCost (
|
|
DfsSite *pDestinationSite,
|
|
PULONG pCost)
|
|
{
|
|
DFSSTATUS Status = ERROR_NOT_FOUND;
|
|
PDFS_SITE_COST_DATA pData = NULL;
|
|
|
|
*pCost = DFS_MAX_COST;
|
|
pData = (PDFS_SITE_COST_DATA)SHashLookupKeyEx(_pSiteCostTable,
|
|
(PVOID)pDestinationSite);
|
|
|
|
if (pData != NULL)
|
|
{
|
|
// See if the entry has expired.
|
|
if (IsTimeToRefresh( pData ))
|
|
{
|
|
// Ask the caller to retry and replace this entry.
|
|
ASSERT(Status == ERROR_NOT_FOUND);
|
|
NOTHING;
|
|
}
|
|
// See if DS had returned an errorneous entry.
|
|
else if (pData->ValidityStatus == ERROR_SUCCESS)
|
|
{
|
|
*pCost = pData->Cost;
|
|
Status = ERROR_SUCCESS;
|
|
}
|
|
// See if it's time for us to try getting this entry again.
|
|
else if (IsTimeToRetry( pData ))
|
|
{
|
|
//
|
|
// Return ERROR_NOT_FOUND because we'd like the caller to retry.
|
|
//
|
|
ASSERT(Status == ERROR_NOT_FOUND);
|
|
NOTHING;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We know at this point that we don't want the caller to retry or refresh.
|
|
// We also know that we need to fallback on using the default max cost.
|
|
//
|
|
*pCost = DFS_MAX_COST;
|
|
Status = ERROR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
DFSSTATUS
|
|
DfsSiteCostCache::RemoveCost(
|
|
DfsSite *pSite)
|
|
{
|
|
NTSTATUS NtStatus;
|
|
|
|
NtStatus = SHashRemoveKey(_pSiteCostTable,
|
|
pSite,
|
|
NULL );
|
|
return RtlNtStatusToDosError( NtStatus );
|
|
|
|
}
|
|
|
|
VOID
|
|
DfsSiteCostCache::InvalidateCache(VOID)
|
|
{
|
|
SHASH_ITERATOR Iter;
|
|
DfsSite *pSite = NULL;
|
|
|
|
pSite = StartSiteEnumerate( &Iter );
|
|
while (pSite != NULL)
|
|
{
|
|
//
|
|
// Remove this item. There's nothing we can do if we hit errors
|
|
// except to keep going.
|
|
//
|
|
(VOID)RemoveCost( pSite );
|
|
pSite = NextSiteEnumerate( &Iter );
|
|
}
|
|
FinishSiteEnumerate( &Iter );
|
|
|
|
DFS_TRACE_LOW( REFERRAL, "SiteCostCache %p: invalidate cache done\n", this);
|
|
}
|
|
|
|
|
|
//------------------------------
|
|
// DfsSiteCostSupport
|
|
//
|
|
// Static constructor
|
|
//------------------------------
|
|
|
|
DFSSTATUS
|
|
DfsSiteCostSupport::DfsCreateSiteCostSupport(
|
|
DfsSiteCostSupport **ppSup )
|
|
{
|
|
DFSSTATUS Status = ERROR_SUCCESS;
|
|
DfsSiteCostSupport *pSup = NULL;
|
|
|
|
*ppSup = NULL;
|
|
|
|
do {
|
|
pSup = new DfsSiteCostSupport;
|
|
if (pSup == NULL)
|
|
{
|
|
Status = ERROR_NOT_ENOUGH_MEMORY;
|
|
break;
|
|
}
|
|
// This doesn't create the hashtable yet. That's done
|
|
// later as needed.
|
|
Status = pSup->Initialize();
|
|
if (Status != ERROR_SUCCESS)
|
|
{
|
|
break;
|
|
}
|
|
|
|
*ppSup = pSup;
|
|
|
|
} while (FALSE);
|
|
|
|
// Error path
|
|
if (Status != ERROR_SUCCESS)
|
|
{
|
|
if (pSup != NULL)
|
|
{
|
|
delete pSup;
|
|
pSup = NULL;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Return a referenced site cache for either lookups or
|
|
// inserts. This guarantees
|
|
DFSSTATUS
|
|
DfsSiteCostSupport::Acquire(
|
|
DfsSiteCostCache **ppCache )
|
|
{
|
|
DFSSTATUS Status = ERROR_SUCCESS;
|
|
ULONG CachesToTrim = 0;
|
|
*ppCache = NULL;
|
|
|
|
//
|
|
// Return a referenced SiteCostCache.
|
|
//
|
|
EnterCriticalSection( &_SiteCostLock );
|
|
{
|
|
do {
|
|
|
|
if (_pCostTable != NULL)
|
|
{
|
|
// Put this at the head of the table.
|
|
MoveToFrontOfMruList();
|
|
break;
|
|
}
|
|
_pCostTable = new DfsSiteCostCache;
|
|
if (_pCostTable == NULL)
|
|
{
|
|
Status = ERROR_NOT_ENOUGH_MEMORY;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Initialize the hashtable of sitenames-to-cost mappings.
|
|
// We'll need to populate this later.
|
|
//
|
|
Status = _pCostTable->Initialize();
|
|
if (Status != ERROR_SUCCESS)
|
|
{
|
|
delete _pCostTable;
|
|
_pCostTable = NULL;
|
|
break;
|
|
}
|
|
|
|
_InUseCount = 1;
|
|
|
|
//
|
|
// Add ourselves to the MRU list because we are a real table now.
|
|
//
|
|
CachesToTrim = InsertInMruList();
|
|
|
|
} while (FALSE);
|
|
|
|
//
|
|
// We are already in the critical section.
|
|
// No need to do atomic increments.
|
|
//
|
|
if (Status == ERROR_SUCCESS)
|
|
{
|
|
_InUseCount++;
|
|
*ppCache = _pCostTable;
|
|
|
|
// We mark the time at acquire time, as opposed to individual lookups and inserts
|
|
// that happen on the table. This assumes that the current reference taken
|
|
// on this cache is not long lived. That indeed is a
|
|
// primary assumption behind this container class.
|
|
_LastAccessTime = GetTickCount();
|
|
}
|
|
ASSERT( Status == ERROR_SUCCESS || _pCostTable == NULL );
|
|
}
|
|
LeaveCriticalSection( &_SiteCostLock );
|
|
|
|
//
|
|
// In a real degenerate case, the following may even throw out what we've just
|
|
// generated above. We are safe though, because we hold a reference
|
|
// until the referral is done.
|
|
//
|
|
if (CachesToTrim)
|
|
{
|
|
TrimSiteCostCaches( CachesToTrim );
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Callers are expected to pair all Acquires above with this Release.
|
|
// This decrements the in use count, and if it reaches zero, which
|
|
// only happens when it's earmarked for deletion, swaps the cost table
|
|
// with a NULL. The actual deletion will then proceed unsynchronized.
|
|
//
|
|
VOID
|
|
DfsSiteCostSupport::Release( VOID )
|
|
{
|
|
DfsSiteCostCache *pOldTable = NULL;
|
|
|
|
EnterCriticalSection( &_SiteCostLock );
|
|
{
|
|
//
|
|
// If this is the last reference, get rid of
|
|
// the cache completely. This will only happen
|
|
// if we are actually trying to get rid this to
|
|
// trim the down the total number of caches
|
|
// sitting around. (See TrimSiteCaches)
|
|
//
|
|
if (_pCostTable != NULL)
|
|
{
|
|
_InUseCount--;
|
|
if (_InUseCount == 0)
|
|
{
|
|
// ASSERT( InMruList == FALSE ); //(unsafe)
|
|
pOldTable = _pCostTable;
|
|
_pCostTable = NULL;
|
|
DfsServerGlobalData.NumSiteCostTables--;
|
|
_LastAccessTime = 0;
|
|
}
|
|
}
|
|
}
|
|
LeaveCriticalSection( &_SiteCostLock );
|
|
|
|
//
|
|
// Delete the site cost table altogether.
|
|
//
|
|
if (pOldTable != NULL)
|
|
{
|
|
pOldTable->ReleaseReference();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// This starts the deletion proceeding of a SiteCostCache.
|
|
// It is safe to call this multiple times, because it'll only
|
|
// take the table off the MRU list only once.
|
|
// The eventual deletion will happen in Release above
|
|
// quite possibly at a later point.
|
|
//
|
|
// DfsSiteNameSupport calls this.
|
|
//
|
|
VOID
|
|
DfsSiteCostSupport::MarkForDeletion( VOID )
|
|
{
|
|
BOOLEAN Delete = FALSE;
|
|
|
|
EnterCriticalSection( &_SiteCostLock );
|
|
{
|
|
if (_pCostTable != NULL)
|
|
{
|
|
Delete = RemoveFromMruList();
|
|
}
|
|
}
|
|
LeaveCriticalSection( &_SiteCostLock );
|
|
|
|
//
|
|
// We needed to get out of the critical section to call Release
|
|
// which will mark it for deletion deletion.
|
|
//
|
|
if (Delete)
|
|
{
|
|
Release();
|
|
}
|
|
return;
|
|
}
|
|
|
|
//
|
|
// The entry is too old to live. Prescribe euthanasia.
|
|
//
|
|
BOOLEAN
|
|
DfsSiteCostSupport::IsExpired( VOID )
|
|
{
|
|
DWORD Interval = DfsServerGlobalData.SiteSupportRefreshInterval;
|
|
BOOLEAN Expired = FALSE;
|
|
|
|
EnterCriticalSection( &_SiteCostLock );
|
|
{
|
|
//
|
|
// No point in expiring a table that doesnt
|
|
// exist.
|
|
//
|
|
if (_pCostTable != NULL)
|
|
{
|
|
Expired = DfsSiteCostCache::IsTime( _LastAccessTime, Interval );
|
|
}
|
|
}
|
|
LeaveCriticalSection( &_SiteCostLock );
|
|
return Expired;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------
|
|
MRU list handling functions of the SiteCostSupport objects.
|
|
The MRU list is guarded by the GlobalDataLock.
|
|
--------------------------------------------------------*/
|
|
|
|
ULONG
|
|
DfsSiteCostSupport::InsertInMruList(VOID)
|
|
{
|
|
ULONG CachesToTrim = 0;
|
|
|
|
DfsAcquireGlobalDataLock();
|
|
{
|
|
InsertHeadList( &DfsServerGlobalData.SiteCostTableMruList, &MruListEntry );
|
|
InMruList = TRUE;
|
|
DfsServerGlobalData.NumSiteCostTablesOnMruList++;
|
|
DfsServerGlobalData.NumSiteCostTables++;
|
|
|
|
//
|
|
// This is iust a convenient time to check this.
|
|
//
|
|
if (DfsServerGlobalData.NumSiteCostTables > DFS_MAX_SITE_COST_CACHES)
|
|
{
|
|
CachesToTrim = DfsServerGlobalData.NumSiteCostTables - DFS_MAX_SITE_COST_CACHES;
|
|
}
|
|
}
|
|
DfsReleaseGlobalDataLock();
|
|
|
|
return CachesToTrim;
|
|
}
|
|
|
|
|
|
VOID
|
|
DfsSiteCostSupport::MoveToFrontOfMruList( VOID )
|
|
{
|
|
DfsAcquireGlobalDataLock();
|
|
{
|
|
if (InMruList == TRUE)
|
|
{
|
|
RemoveEntryList( &MruListEntry );
|
|
InsertHeadList( &DfsServerGlobalData.SiteCostTableMruList, &MruListEntry );
|
|
}
|
|
}
|
|
DfsReleaseGlobalDataLock();
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Returns TRUE if the table was successfully removed
|
|
// from the MRU list.
|
|
//
|
|
BOOLEAN
|
|
DfsSiteCostSupport::RemoveFromMruList( VOID )
|
|
{
|
|
BOOLEAN Delete = FALSE;
|
|
|
|
DfsAcquireGlobalDataLock();
|
|
{
|
|
//
|
|
// If the cache is not on the MRU, then there's
|
|
// nothing to delete.
|
|
//
|
|
if (InMruList == TRUE)
|
|
{
|
|
RemoveEntryList( &MruListEntry );
|
|
DfsServerGlobalData.NumSiteCostTablesOnMruList--;
|
|
|
|
InMruList = FALSE;
|
|
Delete = TRUE;
|
|
}
|
|
}
|
|
DfsReleaseGlobalDataLock();
|
|
|
|
return Delete;
|
|
}
|
|
|
|
DFSSTATUS
|
|
DfsSiteCostSupport::PopLastTableFromMruList(
|
|
DfsSiteCostSupport **pTableToRemove)
|
|
{
|
|
DFSSTATUS Status = ERROR_SUCCESS;
|
|
DfsSiteCostSupport *pTable = NULL;
|
|
PLIST_ENTRY pEntry;
|
|
|
|
*pTableToRemove = NULL;
|
|
|
|
DfsAcquireGlobalDataLock();
|
|
do {
|
|
|
|
// Nothing to do if the MRU list is empty.
|
|
if (IsListEmpty( &DfsServerGlobalData.SiteCostTableMruList ))
|
|
{
|
|
Status = ERROR_NO_MORE_ITEMS;
|
|
break;
|
|
}
|
|
|
|
// Pop the LRU entry off the list
|
|
pEntry = RemoveTailList( &DfsServerGlobalData.SiteCostTableMruList );
|
|
DfsServerGlobalData.NumSiteCostTablesOnMruList--;
|
|
|
|
pTable = CONTAINING_RECORD( pEntry,
|
|
DfsSiteCostSupport,
|
|
MruListEntry );
|
|
pTable->InMruList = FALSE;
|
|
*pTableToRemove = pTable;
|
|
|
|
} while (FALSE);
|
|
|
|
DfsReleaseGlobalDataLock();
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// This is called when we detect that the total number of site cost
|
|
// tables has exceeded its threshold. Currently we check to see if
|
|
// that's the case only when we add a new table (see Acquire).
|
|
//
|
|
ULONG
|
|
DfsSiteCostSupport::TrimSiteCostCaches(
|
|
ULONG MaxCachesToTrim)
|
|
{
|
|
DfsSiteCostSupport *pCacheSup;
|
|
ULONG NumTrims = 0;
|
|
|
|
while (NumTrims < MaxCachesToTrim)
|
|
{
|
|
pCacheSup = NULL;
|
|
(VOID) PopLastTableFromMruList( &pCacheSup );
|
|
|
|
//
|
|
// This just means we have no more items on the MRU list.
|
|
// We must have raced with another because only the tables
|
|
// that are initialized (non-empty) are on the MRU.
|
|
//
|
|
if (pCacheSup == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// This will start the deletion process.
|
|
//
|
|
pCacheSup->Release();
|
|
NumTrims++;
|
|
|
|
// unsafe, but this isn't an exact science
|
|
if (DfsServerGlobalData.NumSiteCostTablesOnMruList <= DFS_MAX_SITE_COST_CACHES)
|
|
{
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
return NumTrims;
|
|
}
|
|
|