|
|
// 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(); }
|