// Copyright (c) 1996-1999 Microsoft Corporation //+------------------------------------------------------------------------- // // Microsoft Windows // // File: voltab.cxx // // Contents: volumes table // // Classes: // // Functions: // // // // History: 16-Dec-96 MikeHill Created. // 23-Jan-97 BillMo Added support for synchronizing clients // after a DC restore. // // Notes: There are two sequence numbers that pertain to each volume. // // The first sequence number is used to synchronize the machine // move logs with the object move table. This sequence number // is relevant to QueryVolume, ClaimVolume, GetVolumeInfo. // // The second sequence number is used to synchronize the // automatic backup of the volume to machine table. // // Codework: // //-------------------------------------------------------------------------- #include "pch.cxx" #pragma hdrstop #include "trksvr.hxx" #include #include #define MAX_MACHINE_BUF_CHARS 256 TCHAR s_RestoreVolumes[] = TEXT("Software\\Microsoft\\LinkTrack\\RestoreVolumes"); class CLdapTimeVolChange { public: CLdapTimeVolChange() { memset(_abPad,0,sizeof(_abPad)); } CLdapTimeVolChange(const CFILETIME &cft) { _cft = cft; memset(_abPad,0,sizeof(_abPad)); } void Swap(); inline BYTE & Byte(int i) { return( ((BYTE*)this)[i] ); } CFILETIME _cft; BYTE _abPad[sizeof(GUID) - sizeof(CFILETIME)]; }; void CLdapTimeVolChange::Swap() { BYTE b; for (int i=0; i Make this lazy. // _SecondsPreviousToNow = _pconfigSvr->GetVolumeQueryPeriod() * _pconfigSvr->GetVolumeQueryPeriods(); _cftCacheLowest.SetToUTC(); _cftCacheLowest.DecrementSeconds( _SecondsPreviousToNow ); // search for all changes since now-period*numperiods (may throw on out of memory) _QueryVolumeChanges( _cftCacheLowest, &_VolMap ); // timer should go off in about 6 hrs CFILETIME cft; cft.IncrementSeconds(VolumeQueryPeriodSeconds); _timerQueryCache.Initialize(this, pwm, 0, VolumeQueryPeriodSeconds, &cft); } #endif } void CVolumeTable::UnInitialize() { if (_fInitializeCalled) { _fInitializeCalled = FALSE; #ifdef VOL_REPL _timerQueryCache.UnInitialize(); DeleteCriticalSection(&_csQueryCache); _VolMap.UnInitialize(); #endif } } #ifdef VOL_REPL void CVolumeTable::Timer( DWORD dwTimerId ) { // redo the query - will leave _VolMap unchanged on error // On low memory exception we should retry the timer. Raise if stopped CFILETIME cftHighest; CFILETIME cft; cft.DecrementSeconds( _SecondsPreviousToNow ); CVolumeMap VolMap; SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); TrkLog((TRKDBG_VOLTAB | TRKDBG_VOLTAB_RESTORE, TEXT("Volume table cache timer"))); // search for all changes since now-period*numperiods (may throw on out of memory) _QueryVolumeChanges( cft, &VolMap ); EnterCriticalSection(&_csQueryCache); _cftCacheLowest = cft; VolMap.MoveTo(&_VolMap); LeaveCriticalSection(&_csQueryCache); } #if DBG void CVolumeTable::PurgeCache() { EnterCriticalSection(&_csQueryCache); _cftCacheLowest = CFILETIME(0); _VolMap.UnInitialize(); LeaveCriticalSection(&_csQueryCache); } #endif #endif HRESULT CVolumeTable::MapResult(int err) const { if (err == LDAP_SUCCESS) { return(S_OK); } else if (err == LDAP_NO_SUCH_OBJECT) { return(TRK_S_VOLUME_NOT_FOUND); } else { TrkRaiseWin32Error(LdapMapErrorToWin32(err)); return(TRK_S_VOLUME_NOT_FOUND); } } HRESULT CVolumeTable::AddVolidToTable( const CVolumeId & volume, const CMachineId & mcidClient, const CVolumeSecret & secret ) { CVolumeId volidZero; CLdapVolumeKeyDn dnKey(GetBaseDn(), volume); CLdapStringMod lsmClass(s_objectClass, s_linkTrackVolEntry, LDAP_MOD_ADD); CLdapSecret ls(secret); CLdapBinaryMod lbmVolumeSecret(s_volumeSecret, (PCHAR)&ls, sizeof(ls), LDAP_MOD_ADD); CLdapBinaryMod lbmMachineId(s_currMachineId, (PCHAR)&mcidClient, sizeof(mcidClient), LDAP_MOD_ADD); CLdapSeqNum lsn; CLdapStringMod lsmSequence(s_seqNotification, lsn, LDAP_MOD_ADD ); CLdapTimeValue ltv; // current time CLdapStringMod lsmTimeVolChange(s_timeVolChange, ltv, LDAP_MOD_ADD); // When writing the zero volume (the refresh sequence number itself), write a 0 for the // sequence number. (Calling _pRefreshSequenceStorage would cause an infinite loop). CLdapRefresh ltvRefresh( volidZero == volume ? 0 : _pRefreshSequenceStorage->GetSequenceNumber() ); CLdapStringMod lsmRefresh(s_timeRefresh, ltvRefresh, LDAP_MOD_ADD); LDAPMod * mods[7]; mods[0] = &lsmClass._mod; mods[1] = &lbmVolumeSecret._mod; mods[2] = &lbmMachineId._mod; mods[3] = &lsmSequence._mod; mods[4] = &lsmTimeVolChange._mod; // Don't write a refresh time for the null volume (this entry is used // to store the current global refresh counter). if( CVolumeId() != volume ) { mods[5] = &lsmRefresh._mod; mods[6] = 0; } else mods[5] = 0; int err = ldap_add_s( Ldap(), dnKey, mods ); if( LDAP_ALREADY_EXISTS == err ) { ldap_delete_s( Ldap(), dnKey ); err = ldap_add_s( Ldap(), dnKey, mods ); } if( LDAP_SUCCESS == err ) _pqtable->IncrementVolumeCountCache(); else TrkLog(( TRKDBG_ERROR, TEXT("Failed AddVolidToDs (%d)"), err )); return MapResult(err); } HRESULT CVolumeTable::PreCreateVolume( const CMachineId & mcidClient, const CVolumeSecret & secret, ULONG cUncountedVolumes, CVolumeId * pvolume ) { int err; RPC_STATUS Status; CMachineId mcidZero(MCID_INVALID); ULONG cVolumes = 0; if(mcidClient != mcidZero && _pqtable->IsVolumeQuotaExceeded(mcidClient, cUncountedVolumes)) { TrkLog((TRKDBG_ERROR, TEXT("Volume quota exceeded for %s"), (const TCHAR*) CDebugString(mcidClient) )); return TRK_E_VOLUME_QUOTA_EXCEEDED; } Status = pvolume->UuidCreate(); if (Status != RPC_S_OK ) { // Since we use the randomized-guid generation algorithm, // we should never get a local guid. TrkAssert( RPC_S_UUID_LOCAL_ONLY != Status ); TrkRaiseWin32Error(Status); } return S_OK; } HRESULT CVolumeTable::QueryVolume( const CMachineId & mcidClient, const CVolumeId & volume, SequenceNumber * pseq, FILETIME * pftLastRefresh ) { HRESULT hr; CMachineId mcidTable; SequenceNumber seq; CVolumeSecret secret; CFILETIME cftRefresh(0); if (volume == CVolumeId()) { TrkRaiseException( TRK_E_INVALID_VOLUME_ID ); } hr = GetVolumeInfo(volume, &mcidTable, &secret, &seq, &cftRefresh ); if (S_OK == hr) { // if (its not the right machine), or (it is the right machine and a nul secret) if (mcidTable != mcidClient || secret == CVolumeSecret()) { return(TRK_S_VOLUME_NOT_OWNED); } *pseq = seq; *pftLastRefresh = cftRefresh; } return(hr); } HRESULT CVolumeTable::FindVolume( const CVolumeId & volume, CMachineId * pmcid ) { HRESULT hr; CMachineId mcidTable; SequenceNumber seq; CVolumeSecret secret; CFILETIME cftRefresh(0); if (volume == CVolumeId()) { TrkRaiseException( TRK_E_INVALID_VOLUME_ID ); } hr = GetVolumeInfo(volume, &mcidTable, &secret, &seq, &cftRefresh ); if (S_OK == hr) { *pmcid = mcidTable; } return(hr); } #if DBG void CVolumeTable::PurgeAll() { int err; TCHAR *apszAttrs[2] = { TEXT("cn"), NULL }; LDAPMessage * pRes; TCHAR tszVolumeTable[MAX_PATH+1]; __try { _tcscpy(tszVolumeTable, s_VolumeTableRDN); _tcscat(tszVolumeTable, GetBaseDn()); err = ldap_search_s( Ldap(), tszVolumeTable, LDAP_SCOPE_ONELEVEL, TEXT("(objectclass=*)"), apszAttrs, 0, // attribute types and values are wanted &pRes ); if (err == LDAP_SUCCESS) { // found it, lets get the attributes out int cEntries = ldap_count_entries(Ldap(), pRes); LDAPMessage * pEntry = ldap_first_entry(Ldap(), pRes); if (pEntry != NULL) { do { TCHAR * ptszDn = ldap_get_dn(Ldap(), pEntry); int errd = ldap_delete_s(Ldap(),ptszDn); TrkLog((TRKDBG_ERROR, TEXT("Purged %s status %d"), ptszDn, errd)); ldap_memfree(ptszDn); } while ( pEntry = ldap_next_entry(Ldap(), pEntry)); } } } __finally { if (err == LDAP_SUCCESS) { ldap_msgfree(pRes); } } } #endif HRESULT CVolumeTable::ClaimVolume( const CMachineId & mcidClient, const CVolumeId & volume, const CVolumeSecret & secretOld, const CVolumeSecret & secretNew, SequenceNumber * pseq, FILETIME * pftLastRefresh ) { HRESULT hr; CMachineId mcidTable; SequenceNumber seq; CVolumeSecret secretCurrent; CVolumeSecret nullSecret; CFILETIME cftRefresh(0); if (volume == CVolumeId()) { TrkRaiseException( TRK_E_INVALID_VOLUME_ID ); } hr = GetVolumeInfo( volume, &mcidTable, &secretCurrent, &seq, &cftRefresh ); if ( S_OK == hr ) { hr = TRK_E_VOLUME_ACCESS_DENIED; if( mcidTable == mcidClient ) hr = SetSecret( volume, secretNew ); else if( secretOld == secretCurrent) hr = SetMachineAndSecret( volume, mcidClient, secretNew ); } if ( S_OK == hr ) { *pseq = seq; } TrkAssert( hr == TRK_E_VOLUME_ACCESS_DENIED || hr == S_OK || hr == TRK_S_VOLUME_NOT_FOUND ); return(hr); } //+---------------------------------------------------------------------------- // // CVolumeTable::DeleteVolume // // Delete an entry from the volume table, but only if the volume is owned // by the calling machine. // //+---------------------------------------------------------------------------- HRESULT CVolumeTable::DeleteVolume( const CMachineId & mcidClient, const CVolumeId & volume ) { HRESULT hr; int LdapError; CMachineId mcidTable; SequenceNumber seq; CVolumeSecret secret; CLdapVolumeKeyDn dnVolume(GetBaseDn(), volume); CFILETIME cftRefresh(0); if (volume == CVolumeId()) { TrkRaiseException( TRK_E_INVALID_VOLUME_ID ); } hr = GetVolumeInfo( volume, &mcidTable, &secret, &seq, &cftRefresh ); if ( S_OK == hr ) { if( mcidTable == mcidClient ) { TrkLog(( TRKDBG_VOLTAB, TEXT("Deleting volume %s"), (const TCHAR*) CDebugString(volume) )); LdapError = ldap_delete_s(Ldap(), dnVolume); if( LDAP_SUCCESS == LdapError ) { hr = S_OK; _pqtable->DecrementVolumeCountCache(); } else hr = HRESULT_FROM_WIN32( LdapMapErrorToWin32(LdapError) ); } else hr = TRK_E_VOLUME_ACCESS_DENIED; } #if DBG if( FAILED(hr) ) TrkLog(( TRKDBG_ERROR, TEXT("Failed attempt to delete volume %s"), (const TCHAR*) CDebugString(volume) )); #endif return(hr); } HRESULT CVolumeTable::GetMachine(const CVolumeId & volume, CMachineId * pmcid) { CVolumeSecret secret; SequenceNumber seq; CFILETIME cftRefresh(0); return GetVolumeInfo( volume, pmcid, &secret, &seq, &cftRefresh ); } HRESULT CVolumeTable::SetMachine(const CVolumeId & volume, const CMachineId & mcid) { CLdapVolumeKeyDn dnKey(GetBaseDn(), volume); CLdapTimeValue ltv; // Defaults to current UTC //ltvc.Swap(); CLdapStringMod lsmTimeVolChange(s_timeVolChange, ltv, LDAP_MOD_REPLACE); CLdapBinaryMod lbmMachineId(s_currMachineId, (PCHAR)&mcid, sizeof(mcid), LDAP_MOD_REPLACE); LDAPMod * mods[3]; HRESULT hr; int err; mods[0] = &lbmMachineId._mod; mods[1] = &lsmTimeVolChange._mod; mods[2] = NULL; err = ldap_modify_s(Ldap(), dnKey, mods); hr = MapResult(err); return(hr); } HRESULT CVolumeTable::SetSecret(const CVolumeId & volume, const CVolumeSecret & secret) { CLdapVolumeKeyDn dnKey(GetBaseDn(), volume); CLdapTimeValue ltv; // Defaults to current UTC CLdapStringMod lsmTimeVolChange(s_timeVolChange, ltv, LDAP_MOD_REPLACE); CLdapSecret ls(secret); CLdapBinaryMod lbmVolumeSecret(s_volumeSecret, (PCHAR)&ls, sizeof(ls), LDAP_MOD_REPLACE); LDAPMod * mods[3]; HRESULT hr; int err; mods[0] = &lbmVolumeSecret._mod; mods[1] = &lsmTimeVolChange._mod; mods[2] = NULL; err = ldap_modify_s(Ldap(), dnKey, mods); hr = MapResult(err); return(hr); } HRESULT CVolumeTable::SetMachineAndSecret(const CVolumeId & volume, const CMachineId & mcid, const CVolumeSecret & secret) { CLdapVolumeKeyDn dnKey(GetBaseDn(), volume); CLdapTimeValue ltv; // Defaults to current UTC CLdapStringMod lsmTimeVolChange(s_timeVolChange, ltv, LDAP_MOD_REPLACE); CLdapBinaryMod lbmMachineId(s_currMachineId, (PCHAR)&mcid, sizeof(mcid), LDAP_MOD_REPLACE); CLdapSecret ls(secret); CLdapBinaryMod lbmVolumeSecret(s_volumeSecret, (PCHAR)&ls, sizeof(ls), LDAP_MOD_REPLACE); LDAPMod * mods[4]; HRESULT hr; int err; mods[0] = &lbmMachineId._mod; mods[1] = &lbmVolumeSecret._mod; mods[2] = &lsmTimeVolChange._mod; mods[3] = NULL; err = ldap_modify_s(Ldap(), dnKey, mods); hr = MapResult(err); return(hr); } //+---------------------------------------------------------------------------- // // CVolumeTable::SetSequenceNumber // // Set the sequence number of a volume entry. This is the value // of we expect to get in the next move-notification for this volume. // (This is used to detect if the trksvr & trkwks get out of sync.) // //+---------------------------------------------------------------------------- HRESULT CVolumeTable::SetSequenceNumber(const CVolumeId & volume, SequenceNumber seq) { int err; HRESULT hr; CLdapVolumeKeyDn dnKey(GetBaseDn(), volume); CLdapSeqNum lsn(seq); CLdapStringMod lsmSequence(s_seqNotification, lsn, LDAP_MOD_REPLACE ); LDAPMod * mods[2]; // Set up the MODs array. mods[0] = &lsmSequence._mod; mods[1] = 0; // Perform the modification. err = ldap_modify_s(Ldap(), dnKey, mods); // Debug output #if DBG if( LDAP_SUCCESS != err ) TrkLog(( TRKDBG_SVR, TEXT("Couldn't set sequence number (%d)"), err )); else TrkLog(( TRKDBG_SVR, TEXT("Set seq %d on %s"), seq, (const TCHAR*) CDebugString(volume) )); #endif // Map back to an HRESULT hr = MapResult(err); return(hr); } HRESULT CVolumeTable::GetVolumeInfo( const CVolumeId & volume, CMachineId * pmcid, CVolumeSecret * psecret, SequenceNumber * pseq, CFILETIME *pcftRefresh ) { // lookup the volume and get the current machine and sequence number if any HRESULT hr; int err; LDAPMessage * pRes = NULL; CLdapVolumeKeyDn dnKey(GetBaseDn(), volume); struct berval ** ppbvMachineId = NULL; //struct berval ** ppbvSeq = NULL; TCHAR ** pptszSeq = NULL; struct berval ** ppbvSecret = NULL; TCHAR ** pptszRefresh = NULL; TCHAR * apszAttrs[5]; __try { apszAttrs[0] = const_cast(s_currMachineId); apszAttrs[1] = const_cast(s_seqNotification); apszAttrs[2] = const_cast(s_volumeSecret); apszAttrs[3] = const_cast(s_timeRefresh); apszAttrs[4] = 0; err = ldap_search_s( Ldap(), dnKey, LDAP_SCOPE_BASE, TEXT("(objectclass=*)"), apszAttrs, 0, // attribute types and values are wanted &pRes ); hr = MapResult(err); if (S_OK == hr) { // found it, lets get the attributes out int cEntries; cEntries = ldap_count_entries(Ldap(), pRes); // Get the entry from the search results. if (cEntries < 1) { TrkLog(( TRKDBG_ERROR, TEXT("GetVolumeInfo: ldap_search for %s succeeded, but with %d entries"), (TCHAR*) dnKey /*CDebugString(volume)._tsz*/, cEntries )); hr = MapResult(LDAP_NO_SUCH_OBJECT); __leave; } LDAPMessage * pEntry = ldap_first_entry(Ldap(), pRes); if (pEntry == NULL) { // This should also never happen. We know at this point that we have // 1 entry in the search result. We'll pretend that it doesn't exist. TrkLog(( TRKDBG_ERROR, TEXT("GetVolumeInfo: ldap_search has one entry, but it couldn't be retrieved") )); hr = MapResult(LDAP_NO_SUCH_OBJECT); __leave; } // Get the machine ID attribute. ppbvMachineId = ldap_get_values_len(Ldap(), pEntry, const_cast(s_currMachineId) ); if( NULL == ppbvMachineId || sizeof(CMachineId) > (*ppbvMachineId)->bv_len ) { // This entry is corrupt, there should always be a mcid attribute. // We'll pretend it doesn't exist for now, and let GC clean it up. hr = MapResult(LDAP_NO_SUCH_OBJECT); __leave; } memcpy( pmcid, (*ppbvMachineId)->bv_val, sizeof(*pmcid) ); // Get the volume secret attribute ppbvSecret = ldap_get_values_len(Ldap(), pEntry, const_cast(s_volumeSecret) ); if( NULL == ppbvSecret || sizeof(CLdapSecret) > (*ppbvSecret)->bv_len ) { // This entry is corrupt, there should always be a secret attribute. // We'll pretend it doesn't exist for now, and let GC clean it up. hr = MapResult(LDAP_NO_SUCH_OBJECT); __leave; } memcpy( psecret, (*ppbvSecret)->bv_val, sizeof(*psecret) ); // Get the sequence number attribute *pseq = 0; pptszSeq = ldap_get_values(Ldap(), pEntry, const_cast(s_seqNotification) ); if (NULL == pptszSeq || CCH_UINT32 < _tcslen(*pptszSeq)) { // The sequence number is missing or invalid. We'll just assume it's zero. TrkLog((TRKDBG_ERROR, TEXT("Sequence number string too long in vol table (vol=%s)"), (const TCHAR*) CDebugString(volume) )); } else if( 1 != _stscanf( *pptszSeq, TEXT("%d"), pseq )) { // Again, assume the sequnce number is zero. TrkLog((TRKDBG_ERROR, TEXT("Invalid sequence number string in vol table (seq=%s, vol=%s)"), *pptszSeq, (const TCHAR*) CDebugString(volume) )); *pseq = 0; } // Get the refresh counter, if it exists. *pcftRefresh = 0; pptszRefresh = ldap_get_values( Ldap(), pEntry, const_cast(s_timeRefresh) ); if( NULL != pptszRefresh ) { SequenceNumber seqRefresh = 0; if( 1 == _stscanf( *pptszRefresh, TEXT("%d"), &seqRefresh )) *pcftRefresh = seqRefresh; } } } __finally { if (NULL != pRes) { ldap_msgfree(pRes); } if (ppbvMachineId != NULL) { ldap_value_free_len(ppbvMachineId); } if (pptszSeq != NULL) { ldap_value_free(pptszSeq); } if (ppbvSecret != NULL) { ldap_value_free_len(ppbvSecret); } if (pptszRefresh != NULL) ldap_value_free(pptszRefresh); if (AbnormalTermination()) { TrkLog(( TRKDBG_ERROR, TEXT("Exception in CVolumeTable::GetVolumeInfo") )); } } return(hr); } // TRUE if exists and touched, FALSE if not existent, exception otherwise. // BUGBUG P2: check ownership of entry being touched. BOOL CVolumeTable::Touch( const CVolumeId & volid ) { if (volid == CVolumeId()) { TrkLog(( TRKDBG_ERROR, TEXT("Null volid passed to CVolumeTable::Touch") )); return( FALSE ); } BOOL fReturn = FALSE; int err; CLdapRefresh ltvRefresh( _pRefreshSequenceStorage->GetSequenceNumber()); CLdapStringMod lsmRefresh( s_timeRefresh, ltvRefresh, LDAP_MOD_REPLACE ); CLdapVolumeKeyDn dnKey(GetBaseDn(), volid); LDAPMod * mods[2]; TCHAR ** pptszRefresh = NULL; LDAPMessage * pEntry = NULL; LDAPMessage* pRes = NULL; __try { // // Check to see if the object already has this sequence number. // TCHAR* rgptszAttrs[2]; rgptszAttrs[0] = const_cast(s_timeRefresh); rgptszAttrs[1] = NULL; err = ldap_search_s(Ldap(), dnKey, LDAP_SCOPE_BASE, TEXT("(ObjectClass=*)"), rgptszAttrs, 0, &pRes); if (err == LDAP_SUCCESS) { // The search call worked, but did we find an object? if( 1 == ldap_count_entries(Ldap(), pRes) ) { // The object already exists pEntry = ldap_first_entry(Ldap(), pRes); if( NULL != pEntry ) { // Get the refresh counter pptszRefresh = ldap_get_values( Ldap(), pEntry, const_cast(s_timeRefresh) ); if( NULL != pptszRefresh ) { SequenceNumber seqRefresh = 0; if( 1 == _stscanf( *pptszRefresh, TEXT("%d"), &seqRefresh )) { // First, long is the GC timer in seconds? LONG lGCTimerInSeconds = _pconfigSvr->GetGCPeriod() // 30 days in seconds / _pconfigSvr->GetGCDivisor(); // 30 // Next, how many ticks is half the period? LONG lWindow = _pconfigSvr->GetGCPeriod() // 30 days (in seconds) / 2 // => 15 days (in seconds) / lGCTimerInSeconds; // => 15 if( seqRefresh + lWindow >= _pRefreshSequenceStorage->GetSequenceNumber() ) { TrkLog(( TRKDBG_GARBAGE_COLLECT | TRKDBG_VOLTAB, TEXT("Not touching volume %s with %d, seq %d already set"), (const TCHAR*) CDebugString(volid), _pRefreshSequenceStorage->GetSequenceNumber(), seqRefresh )); __leave; } } } } } } else if (err == LDAP_NO_SUCH_OBJECT) { TrkLog((TRKDBG_GARBAGE_COLLECT | TRKDBG_VOLTAB, TEXT("Touch: volume %s not found"), (const TCHAR*) CDebugString(volid))); __leave; } // // Set the correct sequence number // mods[0] = &lsmRefresh._mod; mods[1] = NULL; err = ldap_modify_s(Ldap(), dnKey, mods); if (err == LDAP_SUCCESS) { TrkLog((TRKDBG_GARBAGE_COLLECT | TRKDBG_SVR, TEXT("Touch: volume %s touched"), (const TCHAR*) CDebugString(volid))); fReturn = TRUE; __leave; } else if (err == LDAP_NO_SUCH_OBJECT) { TrkLog((TRKDBG_GARBAGE_COLLECT | TRKDBG_SVR, TEXT("Touch:: volume %s doesn't exist"), (const TCHAR*) CDebugString(volid))); __leave; } else if (err == LDAP_NO_SUCH_ATTRIBUTE) { TrkLog((TRKDBG_GARBAGE_COLLECT | TRKDBG_SVR, TEXT("Touch: volume %s attribute not found"), (const TCHAR*) CDebugString(volid))); // deal with old server data CLdapStringMod lsmRefresh( s_timeRefresh, ltvRefresh, LDAP_MOD_ADD ); mods[0] = &lsmRefresh._mod; err = ldap_modify_s(Ldap(), dnKey, mods); } if (err != LDAP_SUCCESS) { TrkLog((TRKDBG_GARBAGE_COLLECT | TRKDBG_SVR, TEXT("Touch:: volume %s gives exceptional error"), (const TCHAR*) CDebugString(volid))); __leave; } } __except( BreakOnDebuggableException() ) { TrkLog(( TRKDBG_ERROR, TEXT("Ignoring exception in CVolumeTable::Touch (%08x)"), GetExceptionCode() )); } if (pptszRefresh != NULL) ldap_value_free(pptszRefresh); if(pRes != NULL) ldap_msgfree(pRes); return( fReturn ); } //+---------------------------------------------------------------------------- // // CVolumeTable::GarbageCollect // // This is called by CTrkSvrSvc when it's time to GC the volume table (daily). // The entries are enumerated, and if too old they are deleted. // //+---------------------------------------------------------------------------- ULONG CVolumeTable::GarbageCollect( SequenceNumber seqCurrent, SequenceNumber seqOldestToKeep, const BOOL * pfAbort ) { CLdapVolumeKeyDn dn(GetBaseDn()); TCHAR * apszAttrs[3]; GC_ENUM_CONTEXT EnumContext; TrkLog(( TRKDBG_VOLTAB | TRKDBG_GARBAGE_COLLECT, TEXT("GC-ing volume table (%d/%d)"), seqCurrent, seqOldestToKeep )); // Set up the attributes for the ldap_search_init_page call. apszAttrs[0] = const_cast(s_Cn); apszAttrs[1] = const_cast(s_timeRefresh); apszAttrs[2] = 0; // Set up all the info that the LdapEnumerate call needs. memset( &EnumContext, 0, sizeof(EnumContext) ); EnumContext.seqOldestToKeep = seqOldestToKeep; EnumContext.seqCurrent = seqCurrent; EnumContext.pfAbort = pfAbort; EnumContext.dwRepetitiveTaskDelay = _pconfigSvr->GetRepetitiveTaskDelay(); EnumContext.pqtable = _pqtable; // Do an ldap_search, calling GcEnumerateCallback for each of the // returned values. if (!LdapEnumerate( Ldap(), // LDAP handle dn, // Base DN LDAP_SCOPE_ONELEVEL, // No recursion TEXT("(objectClass=*)"), // Filter apszAttrs, // Attributes (get CN & refresh time) GcEnumerateCallback, // Called for each iteration &EnumContext )) // Info for GcEnuemrateCallback { TrkRaiseException(TRK_E_SERVICE_STOPPING); } TrkLog(( TRKDBG_GARBAGE_COLLECT | TRKDBG_IDT, TEXT("GC-ed %d entries from the volume table"), EnumContext.cEntries )); // If we actually deleted anything, the cached values // in the quota object are no longer valid. Mark it as // such, so that it will know to re-generate it the next // time it's needed. if( 0 != EnumContext.cEntries ) _pqtable->InvalidateCache(); return EnumContext.cEntries; } ENUM_ACTION GcEnumerateCallback( LDAP * pLdap, LDAPMessage *pMessage, PVOID pvContext, PVOID ) { GC_ENUM_CONTEXT * pContext = (GC_ENUM_CONTEXT *) pvContext; TCHAR * ptszDn = NULL; TCHAR ** pptszValue = NULL; ENUM_ACTION Action = ENUM_KEEP_ENTRY; ULONG ulSequence = 0; // See if we should abort. We shouldn't even be here if we're not // the designated DC. The only way it can happen is if the designated // DC is changed during the enumeration. if( *(pContext->pfAbort) || !pContext->pqtable->IsDesignatedDc() ) { Action = ENUM_ABORT; goto Exit; } // Get the DN of this entry so that we can check for special entries. ptszDn = ldap_get_dn( pLdap, pMessage ); if (ptszDn == NULL) { TrkLog((TRKDBG_GARBAGE_COLLECT, TEXT("Couldn't get DN during GcEnumerateCallback") )); goto Exit; } // CQuotaTable stores special values in the volume table, all prefixed by "QT". // Don't delete those. if( !_tcsnicmp( TEXT("CN=QT"), ptszDn, 5 )) { TrkLog(( TRKDBG_GARBAGE_COLLECT, TEXT("Skipping quota entry in GC (%s)"), ptszDn )); goto Exit; } // The current value of the Refresh counter is stored in volume ID 0. So // don't delete that either. if( !_tcsnicmp( TEXT("CN=00000000000000000000000000000000,"), ptszDn, 35 )) { TrkLog(( TRKDBG_GARBAGE_COLLECT, TEXT("Skipping volid 0 GC (%s)"), ptszDn )); goto Exit; } // Get the refresh time value. pptszValue = ldap_get_values( pLdap, pMessage, const_cast(s_timeRefresh) ); if (pptszValue == NULL) { TrkLog((TRKDBG_GARBAGE_COLLECT, TEXT("Can't find sequence number in %s"), ptszDn)); // This is a corrupted entry that will never get GC-ed, // so we'll delete it now. pContext->cEntries++; Action = ENUM_DELETE_ENTRY; goto Exit; } _stscanf( *pptszValue, TEXT("%d"), &ulSequence ); // Determine if we should delete this entry. if( ulSequence < (ULONG)pContext->seqOldestToKeep ) { Action = ENUM_DELETE_ENTRY; pContext->cEntries++; } else Action = ENUM_KEEP_ENTRY; #if DBG if( ENUM_DELETE_ENTRY == Action ) TrkLog(( TRKDBG_QUOTA, TEXT("Seq to delete: %d/%d"), ulSequence, (ULONG)pContext->seqOldestToKeep )); #endif // Check to see if the entry has an invalid sequence number. It's // invalid if it's bigger than the current value. This can happen if the // special zero entry gets deleted from the volume table for some reason. if( ulSequence > (ULONG)pContext->seqCurrent ) { // Reset the entry's sequence number to the current value. Otherwise it // could be a very long time before it gets GCed. This case should never // happen, but there's no guarantee that someone won't delete the // entry accidentally. CLdapRefresh ltvRefresh( pContext->seqCurrent ); CLdapStringMod lsmRefresh( s_timeRefresh, ltvRefresh, LDAP_MOD_REPLACE ); int err; LDAPMod * mods[2]; mods[0] = &lsmRefresh._mod; mods[1] = NULL; err = ldap_modify_s( pLdap, ptszDn, mods ); TrkLog(( TRKDBG_SVR | TRKDBG_GARBAGE_COLLECT, TEXT("Touched entry with invalid sequence number (%d, %s)"), ulSequence, ptszDn )); } Exit: if( NULL != pptszValue ) ldap_value_free( pptszValue ); if( NULL != ptszDn ) ldap_memfree( ptszDn ); // Be nice to the DS if( 0 != pContext->dwRepetitiveTaskDelay ) Sleep( pContext->dwRepetitiveTaskDelay ); return( Action ); } // returns FALSE if aborted BOOL LdapEnumerate( LDAP * pLdap, TCHAR * ptszBaseDn, ULONG Scope, TCHAR * Filter, TCHAR * Attributes[], PFN_LDAP_ENUMERATE_CALLBACK pCallback, void* UserParam1, void* UserParam2) { LDAPMessage * pResults; LDAPSearch * pSearch; ENUM_ACTION EnumAction = ENUM_KEEP_ENTRY; // Start a paged enumeration using the specified base DN & filter. pSearch = ldap_search_init_page( pLdap, ptszBaseDn, Scope, Filter, Attributes, FALSE, NULL, NULL, 0, 20000, NULL ); if (pSearch != NULL) { int err; ULONG totalCount; // Get the next page of the enumeration while ( EnumAction != ENUM_ABORT && LDAP_SUCCESS == (err = ldap_get_next_page_s( pLdap, pSearch, NULL, 10, &totalCount, &pResults ) && pResults != NULL)) { LDAPMessage * pMessage; LDAPMessage * pFirstMessage; // Loop through the entries on this page. pFirstMessage = pMessage = ldap_first_entry( pLdap, pResults ); while ( EnumAction != ENUM_ABORT && pMessage != NULL ) { // Call the callback to process this entry. EnumAction = (*pCallback)( pLdap, pMessage, UserParam1, UserParam2); if ( EnumAction == ENUM_DELETE_ENTRY ) { // This entry is to be deleted. Increment the entry // count, and if we're not just counting, actually delete it. TCHAR * ptszDn = ldap_get_dn( pLdap, pMessage ); if (ptszDn != NULL) { TrkLog((TRKDBG_ERROR, TEXT("Deleting Dn=%s"), ptszDn)); ldap_delete_s( pLdap, ptszDn ); ldap_memfree( ptszDn ); } } else if(EnumAction == ENUM_KEEP_ENTRY) { } else if(EnumAction == ENUM_DELETE_QUOTAFLAGS) { TCHAR* ptszDn = ldap_get_dn(pLdap, pMessage); if(NULL != ptszDn) { if(UserParam2) { ((CQuotaTable*)UserParam2)->DeleteFlags(pLdap, ptszDn); } } } pMessage = ldap_next_entry( pLdap, pMessage ); } ldap_msgfree( pResults ); if (pFirstMessage == NULL) break; } ldap_search_abandon_page( pLdap, pSearch ); } return(EnumAction != ENUM_ABORT); } #ifdef VOL_REPL void CVolumeTable::QueryVolumeChanges( const CFILETIME & cftFirstChange, CVolumeMap * pVolMap ) { // protect against the data changing under us BOOL fCacheHit; __try { EnterCriticalSection(&_csQueryCache); if (fCacheHit = _cftCacheLowest <= cftFirstChange) { TrkLog((TRKDBG_VOLTAB | TRKDBG_VOLTAB_RESTORE, TEXT("CVolumeTable::QueryVolumeChanges(cftFirstChange=%s) HIT, returning %d change entries from cache"), (const TCHAR*) CDebugString(cftFirstChange), _VolMap.Count())); _VolMap.CopyTo( pVolMap ); } } _finally { LeaveCriticalSection(&_csQueryCache); } if (!fCacheHit) { // the cache was missed... go to the real database. CFILETIME cftHighest; _QueryVolumeChanges( cftFirstChange, pVolMap ); TrkLog((TRKDBG_VOLTAB | TRKDBG_VOLTAB_RESTORE, TEXT("CVolumeTable::QueryVolumeChanges(cftFirstChange=%s) MISS, returning %d entries from full query"), (const TCHAR*) CDebugString(cftFirstChange), pVolMap->Count())); } } #endif // if there are more than zero volume entries, then pVolMap->SetSize and pVolMap->Add will be called, // otherwise pVolMap will not be called and so must be initialized by the caller #ifdef VOL_REPL void CVolumeTable::_QueryVolumeChanges( const CFILETIME & FirstChangeRequested, CVolumeMap * pVolMap ) { // lookup the volume and get the current machine and sequence number if any int err; LDAPMessage * pRes = NULL; CLdapVolumeKeyDn dnKey(GetBaseDn()); struct berval ** ppbvMachineId = NULL; TCHAR ** pptszCnVolumeId = NULL; TCHAR szSearchFilter[256]; TCHAR * apszAttrs[4]; HRESULT hr; CLdapTimeValue ltv(FirstChangeRequested); __try { // // Build up a search filter looking for objects with timeVolChange >= FirstChangeRequested // // (timeVolChange=XXX) // _tcscpy(szSearchFilter, s_timeVolChangeSearch); _tcscat(szSearchFilter, ltv); _tcscat(szSearchFilter, TEXT(")")); // // Build up the list of attributes to query // apszAttrs[0] = s_currMachineId; apszAttrs[1] = s_Cn; apszAttrs[2] = 0; err = ldap_search_s( Ldap(), dnKey, LDAP_SCOPE_ONELEVEL, szSearchFilter, apszAttrs, 0, // attribute types and values are wanted &pRes ); hr = MapResult(err); // // Depending on whether DS sets up a maximum query size, we will need to iterate // performing multiple searches. Initially just use a single query. // if (hr == S_OK) { // found it, lets get the attributes out int cEntries = ldap_count_entries(Ldap(), pRes); if (cEntries != 0) { pVolMap->SetSize(cEntries); LDAPMessage * pEntry = ldap_first_entry(Ldap(), pRes); if (pEntry != NULL) { do { // // for each entry get the // volume id from the CN // machine id // time of last volume change // pptszCnVolumeId = ldap_get_values(Ldap(), pEntry, s_Cn); if (pptszCnVolumeId == NULL) { TrkRaiseException(HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY)); } if (_tcslen(*pptszCnVolumeId) != 32) // length of stringized volume id { // Add code to recover from this. TrkRaiseException(TRK_E_CORRUPT_VOLTAB); } ppbvMachineId = ldap_get_values_len(Ldap(), pEntry, s_currMachineId); if (ppbvMachineId == NULL) { TrkRaiseException(HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY)); } if ((*ppbvMachineId)->bv_len < sizeof(CMachineId)) { // Add code to recover from this TrkRaiseException(TRK_E_CORRUPT_VOLTAB); } CVolumeId volume( *pptszCnVolumeId, TRK_E_CORRUPT_VOLTAB ); CMachineId machine( (*ppbvMachineId)->bv_val, (*ppbvMachineId)->bv_len, TRK_E_CORRUPT_VOLTAB ); pVolMap->Add( volume, machine ); ldap_value_free(pptszCnVolumeId); pptszCnVolumeId = NULL; ldap_value_free_len(ppbvMachineId); ppbvMachineId = NULL; } while ( pEntry = ldap_next_entry(Ldap(), pEntry)); } pVolMap->Compact(); } } } __finally { if (pRes != NULL) { ldap_msgfree(pRes); } if (pptszCnVolumeId != NULL) { ldap_value_free(pptszCnVolumeId); } if (ppbvMachineId != NULL) { ldap_value_free_len(ppbvMachineId); } } TrkLog((TRKDBG_VOLTAB | TRKDBG_VOLTAB_RESTORE, TEXT("_QueryVolumeChanges(filter=%s) got %d changes since %s"), szSearchFilter, pVolMap->Count(), (const TCHAR*) CDebugString(FirstChangeRequested))); } #endif // raises on error, returns number of volumes on this machine DWORD CVolumeTable::CountVolumes( const CMachineId & mcid ) { // lookup the volume and get the current machine and sequence number if any int err; LDAPMessage * pRes = NULL; CLdapVolumeKeyDn dnKey(GetBaseDn()); TCHAR szSearchFilter[256]; TCHAR * pszAppend; TCHAR * aptszAttrs[2]; HRESULT hr; DWORD cVolumes = 0; __try { // // Build up a search filter looking for objects with currMachineId == mcid // // (volTableIdxGUID;binary=XXX) // _tcscpy(szSearchFilter, s_currMachineIdSearch); pszAppend = szSearchFilter + _tcslen(szSearchFilter); // mcid.Stringize(pszAppend); mcid.StringizeAsGuid(pszAppend); *pszAppend++ = TEXT(')'); *pszAppend++ = TEXT('\0'); TrkAssert(_tcslen(szSearchFilter)+1 < ELEMENTS(szSearchFilter)); // // Build up the list of attributes to query // If we ever update to allow large numbers of volumes on a machine, // this should be a paged enumeration. // aptszAttrs[0] = const_cast(s_Cn); aptszAttrs[1] = 0; err = ldap_search_s( Ldap(), dnKey, LDAP_SCOPE_ONELEVEL, szSearchFilter, aptszAttrs, 0, // attribute types and values are wanted &pRes ); hr = MapResult(err); if (hr == S_OK) { // found it, lets get the attributes out cVolumes = ldap_count_entries(Ldap(), pRes); } } __finally { if (pRes != NULL) { ldap_msgfree(pRes); } } TrkLog((TRKDBG_VOLTAB | TRKDBG_VOLTAB_RESTORE, TEXT("CountVolumes(filter=%s) got %d volumes"), szSearchFilter, cVolumes)); return(cVolumes); }