windows-nt/Source/XPSP1/NT/com/svcdlls/trksvcs/trksvr/voltab.cxx
2020-09-26 16:20:57 +08:00

1551 lines
45 KiB
C++

// 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 <commctrl.h>
#include <time.h>
#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<sizeof(*this)/2; i++)
{
b = Byte(i);
Byte(i) = Byte(sizeof(*this)-i-1);
Byte(sizeof(*this)-i-1) = b;
}
}
class CLdapSecret
{
public:
CLdapSecret()
{
memset(_abPad,0,sizeof(_abPad));
}
CLdapSecret(const CVolumeSecret &secret)
{
_secret = secret;
memset(_abPad,0,sizeof(_abPad));
}
CVolumeSecret _secret;
BYTE _abPad[sizeof(GUID) - sizeof(CVolumeSecret)];
};
void
CVolumeTable::Initialize(CTrkSvrConfiguration *pconfigSvr, CQuotaTable* pqtable)
{
_fInitializeCalled = TRUE;
_pqtable = pqtable;
_pconfigSvr = pconfigSvr;
#ifdef VOL_REPL
// Can raise an NTSTATUS so put before fInitializeCalled=TRUE
InitializeCriticalSection(&_csQueryCache);
if (pwm != NULL)
{
//
// Initialize the cache immediately ready for client queries
// Service start time, query may take a while. Dependency on ldap being available.
// => 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<TCHAR*>(s_currMachineId);
apszAttrs[1] = const_cast<TCHAR*>(s_seqNotification);
apszAttrs[2] = const_cast<TCHAR*>(s_volumeSecret);
apszAttrs[3] = const_cast<TCHAR*>(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<TCHAR*>(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<TCHAR*>(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<TCHAR*>(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<TCHAR*>(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<TCHAR*>(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<TCHAR*>(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<TCHAR*>(s_Cn);
apszAttrs[1] = const_cast<TCHAR*>(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<TCHAR*>(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<TCHAR*>(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);
}