windows-nt/Source/XPSP1/NT/com/svcdlls/trksvcs/trksvr/quota.cxx

1334 lines
42 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
// Copyright (c) 1996-1999 Microsoft Corporation
//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// File: quota.cxx
//
// Contents: quota table
//
// Classes:
//
// Functions:
//
// Notes: This table is hidden in the volume table
//
// History: 18-Nov-97 WeiruC Created.
//
//--------------------------------------------------------------------------
#include "pch.cxx"
#pragma hdrstop
#include "trksvr.hxx"
#include <commctrl.h>
#include <time.h>
CQuotaTable::CQuotaTable(CDbConnection& dbc) :
_dbc(dbc),
#if DBG
_cLocks(0),
#endif
_mcid(MCID_LOCAL),
_cftCacheLastUpdated(0),
_cftDesignatedDc(0),
_pvoltab(NULL)
{
_fIsDesignatedDc = FALSE;
_fInitializeCalled = FALSE;
_fStopping = FALSE;
_dwMoveLimit = 0;
_lCachedMoveTableCount = _lCachedVolumeTableCount = 0;
}
CQuotaTable::~CQuotaTable()
{
UnInitialize();
}
void
CQuotaTable::Initialize(CVolumeTable *pvoltab,
CIntraDomainTable *pidt,
CTrkSvrSvc *psvrsvc,
CTrkSvrConfiguration *pcfgsvr )
{
CMachineId mcid;
HRESULT hr;
_cs.Initialize();
#if DBG
_cLocks = 0;
#endif
_fInitializeCalled = TRUE;
_pvoltab = pvoltab;
_pcfgsvr = pcfgsvr;
_pidt = pidt;
_psvrsvc = psvrsvc;
_timer.Initialize(this,
TEXT("NextDcSyncTime"), // Name (this is a persistent timer)
QUOTA_TIMER, // Context ID
_pcfgsvr->GetDcUpdateCounterPeriod(), // Period
CNewTimer::NO_RETRY,
0, 0, 0 ); // Ignored for non-retrying timers
_timer.SetRecurring();
TrkLog(( TRKDBG_VOLUME, TEXT("DC sync timer: %s"),
(const TCHAR*) CDebugString(_timer) ));
// The Reset* routines assume that an entry already exists for
// this DC. If the operation fails because the entry doesn't exist, it
// will add the entry. We ignore any error because if we can't add
// the entry it only means that we are not participating in the race
// for the designated DC.
}
void
CQuotaTable::UnInitialize()
{
if(_fInitializeCalled)
{
_timer.UnInitialize();
_lCachedMoveTableCount = 0;
_lCachedVolumeTableCount = 0;
_cftCacheLastUpdated = CFILETIME(0);
_cs.UnInitialize();
_fInitializeCalled = FALSE;
}
}
//+----------------------------------------------------------------------------
//
// CQuotaTable::Timer
//
// This method is called when _timer fires. If we're the designated
// DC, we'll go through the move table and count the uncounted entries,
// delete the deleted entries, and shorten any moves chains.
//
//+----------------------------------------------------------------------------
PTimerCallback::TimerContinuation
CQuotaTable::Timer(ULONG ulTimer)
{
DWORD dwTrueCount = 0;
Lock();
__try
{
TrkLog((TRKDBG_QUOTA, TEXT("DC synchronization time")));
g_ptrksvr->RaiseIfStopped();
TrkAssert( QUOTA_TIMER == ulTimer );
if( QUOTA_TIMER == ulTimer )
{
if( IsDesignatedDc() )
{
GetTrueCount( &dwTrueCount, BACKGROUND );
TrkLog((TRKDBG_QUOTA, TEXT("Updated counter")));
_psvrsvc->_OperationLog.Add( COperationLog::TRKSVR_QUOTA, S_OK, CMachineId(MCID_INVALID), dwTrueCount );
}
}
}
__except( BreakOnDebuggableException() )
{
// This timer fires often enough that we'll just
// wait until it fires again.
TrkLog(( TRKDBG_WARNING,
TEXT("Ignoring exception in CQuotaTable::Timer (%08x)"),
GetExceptionCode() ));
_psvrsvc->_OperationLog.Add( COperationLog::TRKSVR_QUOTA, GetExceptionCode() );
}
Unlock();
TrkAssert( _timer.IsRecurring() );
return( CONTINUE_TIMER );
}
BOOL
CQuotaTable::IsMoveQuotaExceeded()
{
BOOL fExceeded = TRUE;
Lock();
__try
{
ValidateCache();
// Compare the cached counter with the limit. The cached value should
// never be negative. But if for some reason it is, don't get confused by it
// (i.e. bottom it out at zero). The cached value will get automatically
// corrected when the cache gets refreshed.
if( (DWORD) max(0,_lCachedMoveTableCount) < _dwMoveLimit )
{
fExceeded = FALSE;
}
else
{
// Try to force the cache to be updated
ValidateCache( TRUE );
if( (DWORD) max(0,_lCachedMoveTableCount) < _dwMoveLimit)
{
fExceeded = FALSE;
}
}
if(fExceeded)
{
TrkLog((TRKDBG_QUOTA, TEXT("*** Move table quota exceeded (%li/%lu)"), _lCachedMoveTableCount, _dwMoveLimit ));
}
else
{
TrkLog(( TRKDBG_QUOTA, TEXT("Move quota not exceeded (%li/%lu)"), _lCachedMoveTableCount, _dwMoveLimit ));
}
}
__finally
{
Unlock();
}
return fExceeded;
}
//+----------------------------------------------------------------------------
//
// CQuotaTable::IsVolumeQuotaExceeded
//
// See if this machine is at or over its quota (it can go over in a replicated
// environment). The total number of
// volumes it has is the count of DS entries for the machine, plus
// cUncountedVolumes. cUncountedVolumes is non-zero when a machine
// sends up a request to create multiple volumes, and increments
// as the service iterates through the requests.
//
//+----------------------------------------------------------------------------
BOOL
CQuotaTable::IsVolumeQuotaExceeded( const CMachineId& mcid, ULONG cUncountedVolumes )
{
Lock();
__try
{
// How many entries does this machine have in the DS?
ULONG cVolumes = _pvoltab->CountVolumes(mcid);
// Is it at/over quota?
if( cVolumes + cUncountedVolumes >= _pcfgsvr->GetVolumeLimit() )
{
TrkLog((TRKDBG_QUOTA, TEXT("VOLUME QUOTA EXCEEDED for %s (%d+%d)"),
(const TCHAR*) CDebugString(mcid),
cVolumes, cUncountedVolumes ));
return TRUE;
}
else
TrkLog(( TRKDBG_QUOTA, TEXT("Volume quota not exceeded for %s (%d+%d, %d)"),
(const TCHAR*) CDebugString(mcid),
cVolumes, cUncountedVolumes, _pcfgsvr->GetVolumeLimit() ));
}
__finally
{
Unlock();
}
return FALSE;
}
//+----------------------------------------------------------------------------
//
// ReadAttribute
//
// Given an LDAP pointer, a DN, and an attribute name, read the attribute
// for the entry with that DN.
//
//+----------------------------------------------------------------------------
int
ReadAttribute( LDAP* pldap, const TCHAR *ptszDN, const TCHAR *ptszAttributeName,
TCHAR ***ppptszAttributeValue )
{
int ldapRV = 0;
const TCHAR *rgptszAttrs[] = { ptszAttributeName, NULL };
int cEntries = 0;
LDAPMessage *pRes = NULL;
LDAPMessage *pEntry = NULL;
__try
{
ldapRV = ldap_search_s(pldap,
const_cast<TCHAR*>(ptszDN),
LDAP_SCOPE_BASE,
TEXT("(ObjectClass=*)"),
const_cast<TCHAR**>(rgptszAttrs),
0,
&pRes);
if( LDAP_SUCCESS != ldapRV )
{
TrkLog(( TRKDBG_QUOTA, TEXT("Couldn't find %s (%lu)"), ptszDN, ldapRV ));
__leave;
}
cEntries = ldap_count_entries(pldap, pRes);
if( cEntries < 1 )
{
TrkLog(( TRKDBG_QUOTA, TEXT("No entries returned for %s in %s"), ptszAttributeName, ptszDN ));
ldapRV = LDAP_NO_SUCH_OBJECT;
__leave;
}
pEntry = ldap_first_entry(pldap, pRes);
if(NULL == pEntry)
{
TrkLog(( TRKDBG_QUOTA, TEXT("Entries couldn't be read from result for %s"), ptszDN ));
ldapRV = LDAP_NO_SUCH_OBJECT;
__leave;
}
*ppptszAttributeValue = ldap_get_values(pldap, pEntry, const_cast<TCHAR*>(ptszAttributeName) );
if( NULL == *ppptszAttributeValue )
{
TrkLog(( TRKDBG_QUOTA, TEXT("Couldn't find %s in %s"), ptszAttributeName, ptszDN ));
ldapRV = LDAP_NO_SUCH_OBJECT;
__leave;
}
}
__finally
{
if(NULL != pRes)
{
ldap_msgfree(pRes);
}
}
return( ldapRV );
}
//+----------------------------------------------------------------------------
//
// CQuotaTable::IsDesignatedDc
//
// Determine if this machine is the designated DC. (The designated DC is
// responsible for all modifications to link tracking data in the DS that
// requires a single master). The "RID Master" DC is used as the designated
// DC.
//
//+----------------------------------------------------------------------------
BOOL
CQuotaTable::IsDesignatedDc( BOOL fRaiseOnError )
{
BOOL fSuccess = FALSE;
BOOL fDesignatedDc = FALSE;
HRESULT hr = E_FAIL;
int ldapRV = 0;
int cEntries = 0;
TCHAR **pptszRidManagerReference = NULL;
TCHAR **pptszRoleOwner = NULL;
const TCHAR *rgtszAttrs[] = { s_rIDManagerReference, NULL };
TCHAR *ptszDesignatedDC = NULL, *ptszAfterDesignatedDC = NULL;
CMachineId mcidDesignated, mcidLocal(MCID_LOCAL);
CFILETIME cftNow;
LONGLONG llDelta;
// How old (in seconds) is the _fIsDesignatedDc value?
llDelta = (LONGLONG) cftNow - (LONGLONG) _cftDesignatedDc;
llDelta /= 10000000;
if( llDelta < _pcfgsvr->GetDesignatedDcCacheTTL() )
{
// The cached value is young enough.
//TrkLog(( TRKDBG_QUOTA, TEXT("Cache: %s designated DC"),
// _fIsDesignatedDc ? TEXT("is") : TEXT("isn't") ));
return( _fIsDesignatedDc );
}
// The cached value is too old. Recalculate.
__try
{
// Read the "rIDManagerReference" from the root DC=<domain> object.
ldapRV = ReadAttribute(Ldap(), GetBaseDn(), s_rIDManagerReference, &pptszRidManagerReference );
if( LDAP_SUCCESS != ldapRV )
{
hr = HRESULT_FROM_WIN32( LdapMapErrorToWin32(ldapRV) );
TrkLog(( TRKDBG_QUOTA, TEXT("Couldn't get RID manager reference (%lu)"), ldapRV ));
__leave;
}
TrkLog(( TRKDBG_QUOTA, TEXT("RID manager reference: %s"), *pptszRidManagerReference ));
// The value of the rIDManagerReference is a DN. Read the "fSMORoleOwner" attribute
// from that object.
ldapRV = ReadAttribute( Ldap(), *pptszRidManagerReference, s_fSMORoleOwner, &pptszRoleOwner );
if( LDAP_SUCCESS != ldapRV )
{
hr = HRESULT_FROM_WIN32( LdapMapErrorToWin32(ldapRV) );
TrkLog(( TRKDBG_QUOTA, TEXT("Couldn't get RID role owner (%lu)"), ldapRV ));
__leave;
}
TrkLog(( TRKDBG_QUOTA, TEXT("Role owner: %s"), *pptszRoleOwner ));
// The role owner is of the form
// "CN=NTDS Settings,CN=mikehill4,CN=Servers,CN=Default-FirstSite-Name,CN=Sites,CN=Configuration,DC=trkmikehill,DC=nttest,DC=microsoft,DC=com"
// Pull out the DC's machine name by getting the second "CN=".
ptszDesignatedDC = _tcsstr( *pptszRoleOwner, TEXT("CN=") );
if( NULL == ptszDesignatedDC )
{
hr = HRESULT_FROM_WIN32( LdapMapErrorToWin32(LDAP_NO_SUCH_OBJECT) );
TrkLog(( TRKDBG_QUOTA, TEXT("Couldn't find first component of FSMO role owner") ));
__leave;
}
ptszDesignatedDC = _tcsstr( &ptszDesignatedDC[1], TEXT("CN=") );
if( NULL == ptszDesignatedDC )
{
hr = HRESULT_FROM_WIN32( LdapMapErrorToWin32(LDAP_NO_SUCH_OBJECT) );
TrkLog(( TRKDBG_QUOTA, TEXT("Couldn't find second component of FSMO role owner") ));
__leave;
}
ptszDesignatedDC += 3;
ptszAfterDesignatedDC = _tcsstr( ptszDesignatedDC, TEXT(",CN=") );
if( NULL == ptszAfterDesignatedDC )
{
hr = HRESULT_FROM_WIN32( LdapMapErrorToWin32(LDAP_NO_SUCH_OBJECT) );
TrkLog(( TRKDBG_QUOTA, TEXT("Couldn't find third component of FSMO role owner") ));
__leave;
}
*ptszAfterDesignatedDC = TEXT('\0');
// Are we the same (and therefore the designated) DC?
mcidDesignated = CMachineId(ptszDesignatedDC );
if( mcidDesignated == mcidLocal )
fDesignatedDc = TRUE;
TrkLog(( TRKDBG_QUOTA, TEXT("Designated DC is %s %s"), ptszDesignatedDC,
fDesignatedDc ? TEXT("(this DC)") : TEXT("") ));
_fIsDesignatedDc = fDesignatedDc;
_cftDesignatedDc = cftNow;
fSuccess = TRUE;
}
__finally
{
if( NULL != pptszRidManagerReference )
ldap_value_free( pptszRidManagerReference );
if( NULL != pptszRoleOwner )
ldap_value_free( pptszRoleOwner );
}
if( !fSuccess && fRaiseOnError )
TrkRaiseException( hr );
return( fDesignatedDc );
}
// Returns TRUE if successful, FALSE if entry doesn't exist, raise exception
// otherwise.
BOOL
CQuotaTable::ReadCounter(DWORD* pdwCounter)
{
BOOL fSuccess = FALSE;
struct berval** ppbvCounter = NULL;
TCHAR* rgtszAttrs[2];
LDAPMessage* pRes = NULL;
int ldapRV;
int cEntries = 0;
LDAPMessage* pEntry = NULL;
CLdapQuotaCounterKeyDn dnKeyCounter(GetBaseDn());
__try
{
*pdwCounter = 0;
rgtszAttrs[0] = const_cast<TCHAR*>(s_volumeSecret);
rgtszAttrs[1] = NULL;
ldapRV = ldap_search_s(Ldap(),
dnKeyCounter,
LDAP_SCOPE_BASE,
TEXT("(ObjectClass=*)"),
rgtszAttrs,
0,
&pRes);
if( LDAP_NO_SUCH_OBJECT == ldapRV )
{
TrkLog(( TRKDBG_QUOTA, TEXT("Move table counter doesn't exist") ));
__leave;
}
else if( LDAP_SUCCESS != ldapRV )
{
TrkLog(( TRKDBG_QUOTA, TEXT("Couldn't read move table counter (%d)"), ldapRV ));
__leave;
}
cEntries = ldap_count_entries(Ldap(), pRes);
if( cEntries != 1 )
{
// This should never happen, the counter has an explicit name.
// We'll assume that when the designated DC does a WriteCounter, this
// will be fixed.
TrkLog(( TRKDBG_ERROR, TEXT("Too many move table counters!") ));
__leave;
}
pEntry = ldap_first_entry(Ldap(), pRes);
if(NULL == pEntry)
{
// This should also never happen, we already know the entry count for
// this search result is 1. Again assume that when the designated DC does
// a WriteCounter, this will be fixed.
TrkLog(( TRKDBG_ERROR, TEXT("Entries couldn't be read from result") ));
__leave;
}
ppbvCounter = ldap_get_values_len(Ldap(), pEntry, const_cast<TCHAR*>(s_volumeSecret) );
if (ppbvCounter == NULL)
{
// The designated DC will fix this in WriteCounter.
TrkLog(( TRKDBG_ERROR, TEXT("Move table counter is corrupt, missing %s attribute"),
s_volumeSecret ));
__leave;
}
if ((*ppbvCounter)->bv_len < sizeof(DWORD))
{
// The designated DC will fix this in WriteCounter
TrkLog(( TRKDBG_ERROR, TEXT("Move table counter attribute %s has wrong type (%d)"),
s_volumeSecret, (*ppbvCounter)->bv_len ));
__leave;
}
memcpy( (PCHAR)pdwCounter, (*ppbvCounter)->bv_val, sizeof(DWORD) );
fSuccess = TRUE;
}
__finally
{
if(NULL != pRes)
{
ldap_msgfree(pRes);
}
if (ppbvCounter != NULL)
{
ldap_value_free_len(ppbvCounter);
}
}
return fSuccess;
}
//+----------------------------------------------------------------------------
//
// CQuotaTable::GetTrueCount
//
// Update our cached count of the move table entries. If we're the designated
// DC, this will also clean up the move table: count uncounted entries, delete
// deleted entries, and shorten any move chains.
//
// This routine can be called to run in the background or foreground. In the
// background it does periodic sleeps so that we don't use up the CPU.
//
//+----------------------------------------------------------------------------
void
CQuotaTable::GetTrueCount( DWORD* pdwTrueCount,
EBackgroundForegroundTask eBackgroundForegroundTask )
{
CLdapIdtKeyDn dnKey(GetBaseDn());
int ldapRV;
TCHAR* rgptszAttrs[3];
TCHAR ldapSearchFilter[256];
DWORD dwCounter = 0;
BOOL fDoWriteCounter = FALSE;
BOOL fNoExistingCounter = FALSE;
TRUE_COUNT_ENUM_CONTEXT Context;
__try
{
// Read the current counter.
if( !ReadCounter( &dwCounter ) )
{
// There is no counter. We'll enumerate everything.
TrkLog(( TRKDBG_QUOTA, TEXT("Getting move table count") ));
_tcscpy( ldapSearchFilter, TEXT("(ObjectClass=*)") );
fDoWriteCounter = TRUE;
Context.fCountAll = TRUE;
fNoExistingCounter = TRUE;
}
else
{
// Only enumerate the uncounted and/or deleted entries.
TrkLog(( TRKDBG_QUOTA, TEXT("Getting delta move table count") ));
_tcscpy(ldapSearchFilter, TEXT("("));
_tcscat(ldapSearchFilter, s_oMTIndxGuid);
_tcscat(ldapSearchFilter, TEXT("=*"));
_tcscat(ldapSearchFilter, TEXT(")"));
Context.fCountAll = FALSE;
}
rgptszAttrs[0] = const_cast<TCHAR*>(s_currentLocation);
rgptszAttrs[1] = const_cast<TCHAR*>(s_birthLocation);
rgptszAttrs[2] = NULL;
Context.cDelta = 0;
Context.dwRepetitiveTaskDelay = (BACKGROUND == eBackgroundForegroundTask)
? _pcfgsvr->GetRepetitiveTaskDelay()
: 0;
Context.dwPass = Context.FIRST_PASS;
// Enumerate the move table, subject to the search filter determined
// above.
if( !LdapEnumerate( Ldap(),
dnKey,
LDAP_SCOPE_ONELEVEL,
ldapSearchFilter,
rgptszAttrs,
MoveTableEnumCallback,
&Context,
this) )
{
TrkRaiseException(TRK_E_SERVICE_STOPPING);
}
// If we're the designated DC, we need to do a second pass.
// The first pass may have done some string shortening,
// and in the process marked some entries for delete. We need to
// go through now and remove those entries.
if( IsDesignatedDc() )
{
TrkLog(( TRKDBG_QUOTA, TEXT("Getting delta move table count (pass 2)") ));
Context.dwPass = Context.SECOND_PASS;
// We only need to count the delta this time
_tcscpy(ldapSearchFilter, TEXT("("));
_tcscat(ldapSearchFilter, s_oMTIndxGuid);
_tcscat(ldapSearchFilter, TEXT("=*"));
_tcscat(ldapSearchFilter, TEXT(")"));
Context.fCountAll = FALSE;
if( !LdapEnumerate( Ldap(),
dnKey,
LDAP_SCOPE_ONELEVEL,
ldapSearchFilter,
rgptszAttrs,
MoveTableEnumCallback,
&Context,
this) )
{
TrkRaiseException(TRK_E_SERVICE_STOPPING);
}
}
TrkLog((TRKDBG_QUOTA, TEXT("Uncounted entries ---- %d"), Context.cDelta));
TrkLog((TRKDBG_QUOTA, TEXT("Counter ---- %d"), dwCounter));
}
__finally
{
if( Context.cDelta != 0 )
fDoWriteCounter = TRUE;
*pdwTrueCount = max( 0, (LONG)dwCounter + Context.cDelta );
// If we're the designated DC, we may need to write the counter.
// But only do so if this is a normal termination, or if there
// wasn't already a counter. This covers the three cases:
//
// 1) The counter didn't already exist, so we were enumerating everything.
// a) Normal termination, so we should update the counter
// with the newly calculated value.
// b) Abnormal termination, so we shouldn't update the counter.
// That way we'll know to do the count again later.
//
// 2) The counter already existed, so we were counting the uncounted
// entries. In this case, the entries were being updated to
// be counted as we went through the enumeration. So whether
// or not we had a normal termination, we need to updated the
// counter with what we did so far.
//
// The only reason we expect an exception is in the case of a
// service stop.
if( IsDesignatedDc()
&&
fDoWriteCounter
&&
( !AbnormalTermination() || !fNoExistingCounter ) )
{
WriteCounter(*pdwTrueCount);
}
TrkLog((TRKDBG_QUOTA, TEXT("True count ---- %d"), *pdwTrueCount));
}
}
//+----------------------------------------------------------------------------
//
// CQuotaTable::OnMoveTableGcComplete
//
// This method is called after a GC of the move table, telling us how
// many entries were deleted. We use this to update the move counter
// (if it still exists).
//
//+----------------------------------------------------------------------------
void
CQuotaTable::OnMoveTableGcComplete( ULONG cEntriesDeleted )
{
DWORD dwCounter = 0;
if( 0 == cEntriesDeleted )
return;
if( ReadCounter( &dwCounter ) )
{
TrkLog(( TRKDBG_QUOTA, TEXT("Old move counter was %d"), dwCounter ));
if( dwCounter >= cEntriesDeleted )
dwCounter -= cEntriesDeleted;
else
dwCounter = 0;
WriteCounter(dwCounter);
TrkLog(( TRKDBG_QUOTA, TEXT("New move counter is %d"), dwCounter ));
}
}
void
CQuotaTable::WriteCounter(DWORD dwCounter)
{
LDAPMod* mods[3];
int ldapRV;
CLdapQuotaCounterKeyDn dnKeyCounter(GetBaseDn());
CLdapBinaryMod lbmCounter(s_volumeSecret, (PCHAR)&dwCounter, sizeof(DWORD), LDAP_MOD_REPLACE);
mods[0] = &lbmCounter._mod;
mods[1] = NULL;
ldapRV = ldap_modify_s(Ldap(), dnKeyCounter, mods);
if( LDAP_SUCCESS != ldapRV )
{
if( LDAP_NO_SUCH_OBJECT != ldapRV )
{
// There's some kind of problem with the existing counter.
// Delete and re-create it.
ldap_delete_s( Ldap(), dnKeyCounter );
}
CLdapStringMod lsmClass(s_objectClass, s_linkTrackVolEntry, LDAP_MOD_ADD);
CLdapBinaryMod lbmCounter(s_volumeSecret, (PCHAR)&dwCounter, sizeof(DWORD), LDAP_MOD_ADD);
mods[0] = &lsmClass._mod;
mods[1] = &lbmCounter._mod;
mods[2] = NULL;
ldapRV = ldap_add_s(Ldap(), dnKeyCounter, mods);
TrkLog(( TRKDBG_QUOTA, TEXT("Created counter %d, ldap returned %d"), dwCounter, ldapRV ));
}
else
TrkLog((TRKDBG_QUOTA, TEXT("Wrote counter %d, ldap returned %d"), dwCounter, ldapRV));
}
HRESULT
CQuotaTable::DeleteCounter()
{
HRESULT hr = S_OK;
int LdapError = 0;
CLdapQuotaCounterKeyDn dnKeyCounter(GetBaseDn());
LdapError = ldap_delete_s(Ldap(), dnKeyCounter);
if( LDAP_SUCCESS == LdapError )
hr = S_OK;
else
hr = HRESULT_FROM_WIN32( LdapMapErrorToWin32(LdapError) );
if( FAILED(hr) )
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't delete move-table counter (%08x)"), hr ));
else
TrkLog(( TRKDBG_QUOTA, TEXT("Deleted move-table counter") ));
return( hr );
}
//+----------------------------------------------------------------------------
//
// CQuotaTable::ValidateCache
//
// Validate the cached move and volume counts. They are valid if they exist
// and are newer than the max time-to-live. If they aren't valid, they are
// re-calculated. They are also recalculated if the caller sets the
// fForceHint parameter, and the cache is at least of minimum age.
//
//+----------------------------------------------------------------------------
void
CQuotaTable::ValidateCache( BOOL fForceHint )
{
// How old is the cache in seconds?
DWORD dwDelta = ( (LONGLONG)CFILETIME() - (LONGLONG)_cftCacheLastUpdated ) / 10000000;
// The cache should be updated if any of the following are true
if( 0 == _cftCacheLastUpdated // We have no cached values
||
dwDelta > _pcfgsvr->GetCacheMaxTimeToLive() // The cached values are too old
||
// The cached values are old enough, and the
// the caller wants us to be aggressive.
fForceHint && (dwDelta > _pcfgsvr->GetCacheMinTimeToLive()) )
{
// Yes, we need to update the cached values.
CLdapVolumeKeyDn dnKey(GetBaseDn());
TCHAR* rgptszAttrs[2];
DWORD cVolumeTableEntries = 0;
TrkLog(( TRKDBG_QUOTA, TEXT("Updating quota caches (%s)"),
fForceHint ? TEXT("forced") : TEXT("not forced") ));
// Get the true count of move table entries.
GetTrueCount( (DWORD*) &_lCachedMoveTableCount, FOREGROUND );
TrkAssert( _lCachedMoveTableCount >= 0 );
// Get the count of volume table entries
rgptszAttrs[0] = const_cast<TCHAR*>(s_Cn);
rgptszAttrs[1] = NULL;
if( !LdapEnumerate( Ldap(),
dnKey,
LDAP_SCOPE_ONELEVEL,
TEXT("(&(ObjectClass=*)(!(cn=QT*)))"),
rgptszAttrs,
VolumeTableEnumCallback,
&cVolumeTableEntries,
this) )
{
TrkRaiseException( TRK_E_SERVICE_STOPPING );
}
_lCachedVolumeTableCount = (LONG) cVolumeTableEntries;
// Calculate the move table limit
_dwMoveLimit = CalculateMoveLimit();
// Show that the cache is up-to-date
_cftCacheLastUpdated.SetToUTC();
}
TrkLog(( TRKDBG_QUOTA, TEXT("Cache: MoveCount=%d, MoveLimit=%d, VolCount=%d"),
_lCachedMoveTableCount, _dwMoveLimit, _lCachedVolumeTableCount ));
}
DWORD
CQuotaTable::CalculateMoveLimit() // Doesn't raise
{
DWORD dwMoveLimit = 0;
LONG lVolumeCount = max( 0, _lCachedVolumeTableCount );
if( lVolumeCount <= _pcfgsvr->GetMoveLimitTransition() )
dwMoveLimit = lVolumeCount * _pcfgsvr->GetMoveLimitPerVolumeLower();
else
{
dwMoveLimit = _pcfgsvr->GetMoveLimitTransition()
*
_pcfgsvr->GetMoveLimitPerVolumeLower();
dwMoveLimit += ( lVolumeCount - _pcfgsvr->GetMoveLimitTransition() )
*
_pcfgsvr->GetMoveLimitPerVolumeUpper();
}
return( dwMoveLimit );
}
HRESULT
CQuotaTable::ReadFlags(LDAP* pLdap, TCHAR* dnKey, BYTE* bFlags)
{
struct berval** ppbvFlags = NULL;
TCHAR* rgptszAttrs[2];
LDAPMessage* pRes = NULL;
int ldapRV;
int cEntries;
LDAPMessage* pEntry = NULL;
HRESULT hr = S_OK;
__try
{
*bFlags = 0x0;
rgptszAttrs[0] = const_cast<TCHAR*>(s_oMTIndxGuid);
rgptszAttrs[1] = NULL;
ldapRV = ldap_search_s(pLdap,
dnKey,
LDAP_SCOPE_BASE,
TEXT("(ObjectClass=*)"),
rgptszAttrs,
0,
&pRes);
hr = HRESULT_FROM_WIN32(LdapMapErrorToWin32(ldapRV));
if(ldapRV != LDAP_SUCCESS)
__leave;
cEntries = ldap_count_entries(pLdap, pRes);
if(cEntries != 1)
{
// This shouldn't happen. The caller asked for flags on an entry
// which doesn't exist.
TrkLog(( TRKDBG_ERROR, TEXT("ReadFlags, entry doesn't exist: %s"), dnKey ));
hr = HRESULT_FROM_WIN32(LdapMapErrorToWin32(LDAP_NO_SUCH_OBJECT));
__leave;
}
pEntry = ldap_first_entry(pLdap, pRes);
if(NULL == pEntry)
{
// This should never happen. We already know that there's an entry.
TrkLog(( TRKDBG_ERROR, TEXT("ReadFlags, couldn't get first entry on %s"), dnKey ));
hr = HRESULT_FROM_WIN32(LdapMapErrorToWin32(LDAP_NO_SUCH_OBJECT));
__leave;
}
ppbvFlags = ldap_get_values_len(pLdap, pEntry, const_cast<TCHAR*>(s_oMTIndxGuid) );
if(NULL == ppbvFlags)
{
hr = HRESULT_FROM_WIN32(LdapMapErrorToWin32(LDAP_NO_SUCH_ATTRIBUTE));
__leave;
}
if( (*ppbvFlags)->bv_len < sizeof(BYTE)
||
((*ppbvFlags)->bv_val == NULL) )
{
// The best we can do is pretend the attribute doesn't exist.
TrkLog(( TRKDBG_ERROR, TEXT("ReadFlags, attribute is wrong type or missing (%d/%p)"),
(*ppbvFlags)->bv_len, (*ppbvFlags)->bv_val ));
hr = HRESULT_FROM_WIN32(LdapMapErrorToWin32(LDAP_NO_SUCH_ATTRIBUTE));
__leave;
}
*bFlags = *(BYTE*)(*ppbvFlags)->bv_val;
hr = S_OK;
}
__finally
{
if(pRes != NULL)
{
ldap_msgfree(pRes);
}
if(ppbvFlags != NULL)
{
ldap_value_free_len(ppbvFlags);
}
}
return hr;
}
void
CQuotaTable::DeleteFlags(LDAP* pLdap, TCHAR* dnKey)
{
BYTE bFlags = 0x0;
int err;
Lock();
__try
{
CLdapBinaryMod lbm(s_oMTIndxGuid, NULL, 0, LDAP_MOD_DELETE );//reinterpret_cast<PCCH>(&bFlags), sizeof(BYTE), LDAP_MOD_DELETE);
LDAPMod* mods[2];
mods[0] = &lbm._mod;
mods[1] = NULL;
err = ldap_modify_s(pLdap, dnKey, mods);
TrkLog((TRKDBG_QUOTA, TEXT("Deleted flag %x on entry %s, ldap returned %d"), bFlags, dnKey, err));
}
__finally
{
Unlock();
}
}
// TRUE => Found, FALSE => Not found, Raise otherwise
BOOL
CQuotaTable::UpdateFlags(LDAP* pLdap, TCHAR* dnKey, BYTE bFlags)
{
BOOL fFound = FALSE;
BYTE bOrigFlags;
HRESULT hr;
LDAPMod* mods[2];
int err;
TrkAssert(!(bFlags & QFLAG_UNCOUNTED && bFlags & QFLAG_DELETED));
Lock();
__try
{
hr = ReadFlags(pLdap, dnKey, &bOrigFlags);
if(hr == (HRESULT) HRESULT_FROM_WIN32(LdapMapErrorToWin32(LDAP_NO_SUCH_ATTRIBUTE)))
{
CLdapBinaryMod lbm(s_oMTIndxGuid, reinterpret_cast<PCCH>(&bFlags), sizeof(BYTE), LDAP_MOD_ADD);
mods[0] = &lbm._mod;
mods[1] = NULL;
err = ldap_modify_s(pLdap, dnKey, mods);
TrkLog((TRKDBG_QUOTA, TEXT("Added flag %01x on entry %s, ldap returned %d"), bFlags, dnKey, err));
}
else if(hr == S_OK)
{
if(bOrigFlags == QFLAG_UNCOUNTED && bFlags == QFLAG_DELETED)
{
bOrigFlags = bOrigFlags | bFlags;
CLdapBinaryMod lbm(s_oMTIndxGuid, reinterpret_cast<PCCH>(&bOrigFlags), sizeof(BYTE), LDAP_MOD_REPLACE);
mods[0] = &lbm._mod;
mods[1] = NULL;
err = ldap_modify_s(pLdap, dnKey, mods);
TrkLog((TRKDBG_QUOTA, TEXT("Updated flag to %01x on entry %s, ldap returned %d"), bOrigFlags, dnKey, err));
}
else if(bOrigFlags == QFLAG_DELETED && bFlags == QFLAG_UNCOUNTED)
{
DeleteFlags(pLdap, dnKey);
}
}
else
{
__leave;
}
fFound = TRUE;
}
__finally
{
Unlock();
}
return( fFound );
}
//+----------------------------------------------------------------------------
//
// CQuotaTable::DeleteOrphanedEntries
//
// This routine is given a list of move table entries. The birth entry has
// been updated by the caller to point to the final entry. As a result, all
// the other entries are orphaned and must be deleted.
//
//+----------------------------------------------------------------------------
BOOL
CQuotaTable::DeleteOrphanedEntries( const CDomainRelativeObjId rgdroidList[], ULONG cSegments,
const CDomainRelativeObjId &droidBirth,
const CDomainRelativeObjId &droidCurrent )
{
BOOL fCurrentNeedsToBeDeleted = FALSE;
for( int j=0; j < cSegments; j++ )
{
// Don't do anything if this is the birth entry
if( rgdroidList[j] != droidBirth )
{
// If this is the current entry we don't delete it, but tell the
// caller about it.
if( rgdroidList[j] == droidCurrent )
{
fCurrentNeedsToBeDeleted = TRUE;
}
// Otherwise, we delete it directly
else if( _pidt->Delete( rgdroidList[j] ) )
{
TrkLog((TRKDBG_QUOTA,
TEXT("Orphaned segment deleted %s"),
static_cast<const TCHAR*>(CAbbreviatedIDString(rgdroidList[j])) ));
}
else
{
TrkLog((TRKDBG_QUOTA|TRKDBG_WARNING,
TEXT("Orphaned segment failed to delete %s"),
static_cast<const TCHAR*>(CAbbreviatedIDString(rgdroidList[j])) ));
}
}
}
return( fCurrentNeedsToBeDeleted );
}
//+----------------------------------------------------------------------------
//
// CQuotaTable::ShortenString
//
// Given an entry in the move table, see if it is part of a string that
// needs to be shortened, and if so do the shortening.
//
//+----------------------------------------------------------------------------
void
CQuotaTable::ShortenString( LDAP* pLdap, LDAPMessage* pMessage, BYTE *pbFlags,
const CDomainRelativeObjId &droidCurrent )
{
CDomainRelativeObjId droidBirth;
CDomainRelativeObjId droidScan;
CDomainRelativeObjId droidNext;
CDomainRelativeObjId rgdroidList[MAX_SHORTENABLE_SEGMENTS];
int cSegments = 0;
TCHAR *ptszCurrentCN = NULL;
__try
{
// Attempt to read the entry and get its birth ID.
if( _pidt->Query( pLdap, pMessage, droidCurrent, &droidNext, &droidBirth ) )
{
// We have a successful read.
BOOL fStringDeleted = FALSE;
// Scan forward from that birth to see if we can find multiple entries.
droidScan = droidBirth;
_psvrsvc->Scan( NULL, NULL, droidBirth,
rgdroidList, ELEMENTS(rgdroidList), &cSegments,
&droidScan, &fStringDeleted );
// If this is a multi-segment string, reduce it to a single segment.
if( cSegments > 1 )
{
// Was the last segment of the string deleted?
if( fStringDeleted )
{
// Yes, that means that the entired string has been deleted.
TrkLog(( TRKDBG_QUOTA, TEXT("Deleting string starting at %s"),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidBirth)) ));
if( droidCurrent == droidBirth )
*pbFlags |= QFLAG_DELETED;
else
_pidt->Delete( droidBirth );
}
// Otherwise, map from the birth to the last droid.
else if( !_pidt->Modify( droidBirth, droidScan, droidBirth ))
{
TrkLog(( TRKDBG_QUOTA|TRKDBG_WARNING, TEXT("Couldn't shorten %s -> %s"),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidBirth)),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidScan)) ));
__leave;
}
else
{
TrkLog(( TRKDBG_QUOTA, TEXT("Shortened %s -> %s [%s]"),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidBirth)),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidScan)),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidBirth)) ));
}
// The birth entry has been shortened, or deleted. So we can delete the
// rest of the entries.
if( DeleteOrphanedEntries( rgdroidList, cSegments,
droidBirth, // Don't delete this one
droidCurrent // Or this one
))
{
// Amongst the orphans in need of a delete is the current
// entry. Set the deleted flag, and the caller will take
// care of removing the entry.
TrkLog(( TRKDBG_QUOTA, TEXT("Current is orphaned and will be deleted") ));
*pbFlags |= QFLAG_DELETED;
}
} // if( cSegments > 1 )
}
}
__except( BreakOnDebuggableException() )
{
TrkLog(( TRKDBG_QUOTA, TEXT("Ignoring exception from Scan in FlaggedEntriesEnumCallback (%08x)"),
GetExceptionCode() ));
}
if( NULL != ptszCurrentCN )
ldap_memfree(ptszCurrentCN);
}
//+----------------------------------------------------------------------------
//
// CQuotaTable::MoveTableEnumCallback
//
// When the move table is enumerated, this routine is passed to LdapEnumerate
// as the callback function. If we're the designated DC, this function
// takes care of uncounted/deleted entries and shortens move table strings.
// Whether or not we're the designated DC, we update the count value,
// according to the uncounted/deleted entries, in the pvContext structure.
//
//+----------------------------------------------------------------------------
ENUM_ACTION // static
CQuotaTable::MoveTableEnumCallback(LDAP* pLdap, LDAPMessage* pMessage, void* pvContext, void* pvThis )
{
struct berval ** ppbvFlags = NULL;
BYTE bFlags = 0;
ENUM_ACTION action = ENUM_KEEP_ENTRY;
TRUE_COUNT_ENUM_CONTEXT *pContext = (TRUE_COUNT_ENUM_CONTEXT*) pvContext;
CQuotaTable *pThis = (CQuotaTable*)pvThis;
TCHAR *ptszCurrentDN = NULL;
CDomainRelativeObjId droidCurrent;
if( pThis->_fStopping )
return( ENUM_ABORT );
__try
{
// Read the quota flags for this entry. We can't read them out of the
// enumeration buffer, because the flags can get updated by the enumeration
// and the enumeration buffer doesn't reflect the change.
ptszCurrentDN = ldap_get_dn( pLdap, pMessage );
if (ptszCurrentDN == NULL)
{
TrkLog((TRKDBG_ERROR, TEXT("Couldn't get DN") ));
TrkRaiseLastError();
}
droidCurrent.ReadLdapIdtKeyBuffer(ptszCurrentDN);
TrkLog(( TRKDBG_QUOTA, TEXT("Enumerating %s"),
static_cast<const TCHAR*>(CAbbreviatedIDString(droidCurrent)) ));
pThis->ReadFlags(pLdap, ptszCurrentDN, &bFlags );
// Count this entry if we're counting everything, or if we're only
// counting flagged values and this one is flagged as uncounted.
if( pContext->fCountAll || (bFlags & QFLAG_UNCOUNTED) )
pContext->cDelta++;
// If this is uncounted and we're the designated DC, then it's
// counted now and we can delete the flags attribute.
if( (bFlags & QFLAG_UNCOUNTED) && pThis->IsDesignatedDc() )
action = ENUM_DELETE_QUOTAFLAGS;
// If we're the designated DC, we can do some additional cleanup here.
// It's because of this work that we need two passes; one to do the
// cleanup, and one to delete the entries that this cleanup marked
// for deletion (by calling _pidt->Delete).
if( pThis->IsDesignatedDc() && pContext->FIRST_PASS == pContext->dwPass )
{
pThis->ShortenString( pLdap, pMessage, &bFlags, droidCurrent );
}
// If this entry is marked for delete, decrement the count.
// Note that if the entry was marked uncounted and deleted,
// we incremented at the top and will decrement here for the
// correct change of zero.
if( bFlags & QFLAG_DELETED )
{
pContext->cDelta--;
// If we're the designated DC, we can delete the entry.
if( pThis->IsDesignatedDc() )
action = ENUM_DELETE_ENTRY;
}
}
__finally
{
if(ppbvFlags != NULL)
{
ldap_value_free_len(ppbvFlags);
}
if( NULL != ptszCurrentDN )
ldap_memfree( ptszCurrentDN );
}
// Be nice to the DS
if( 0 != pContext->dwRepetitiveTaskDelay )
Sleep( pContext->dwRepetitiveTaskDelay );
return action;
}
ENUM_ACTION // static
CQuotaTable::VolumeTableEnumCallback( LDAP * pLdap, LDAPMessage * pResult, void* pcEntries, void* pvThis )
{
CQuotaTable* pThis = (CQuotaTable*)pvThis;
if( pThis->_fStopping )
return( ENUM_ABORT );
(*((DWORD*)pcEntries))++;
return ENUM_KEEP_ENTRY;
}
void
CQuotaTable::Statistics( TRKSVR_STATISTICS *pTrkSvrStatistics )
{
pTrkSvrStatistics->dwMoveLimit = _dwMoveLimit;
pTrkSvrStatistics->dwCachedVolumeTableCount = (DWORD) _lCachedVolumeTableCount;
pTrkSvrStatistics->dwCachedMoveTableCount = (DWORD) _lCachedMoveTableCount;
pTrkSvrStatistics->ftCacheLastUpdated = _cftCacheLastUpdated;
pTrkSvrStatistics->fIsDesignatedDc = IsDesignatedDc();
}