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

2128 lines
67 KiB
C++

// Copyright (c) 1996-1999 Microsoft Corporation
//+============================================================================
//
// volmgr.cxx
//
// This file implements the CVolumeManager class. That class maintains
// a list of CVolume objects.
//
//+============================================================================
#include <pch.cxx>
#pragma hdrstop
#include "trkwks.hxx"
#include <dbt.h>
#define THIS_FILE_NUMBER VOLMGR_CXX_FILE_NO
void
CVolumeManager::Initialize(CTrkWksSvc * pTrkWks,
const CTrkWksConfiguration *pTrkWksConfiguration,
PLogCallback * pLogCallback,
SERVICE_STATUS_HANDLE ssh
#if DBG
, CTestSync * pTunnelTest
#endif
)
{
BOOL fReg = FALSE;
HKEY hKey;
OBJECT_ATTRIBUTES oa;
UNICODE_STRING name;
CSystemSD ssd;
NTSTATUS Status;
TrkLog(( TRKDBG_VOLUME, TEXT("Initializing the volume list") ));
TrkAssert( !_fInitialized );
_csVolumeNodeList.Initialize();
_fInitialized = TRUE;
_pTrkWks = pTrkWks;
_pTrkWksConfiguration = pTrkWksConfiguration;
_pVolumeNodeListHead = NULL;
__try
{
// This timer is started when we unexpectedly lose our volume handles.
// When it fires, we try to reopen them. After a number of such retries,
// we give up and stop the timer.
_timerObjIdIndexReopen.Initialize(
this,
NULL, // No name (non-persistent timer)
VOLTIMER_OBJID_INDEX_REOPEN, // Context ID
pTrkWksConfiguration->GetObjIdIndexReopen(),
CNewTimer::RETRY_WITH_BACKOFF,
pTrkWksConfiguration->GetObjIdIndexReopenRetryMin(),
pTrkWksConfiguration->GetObjIdIndexReopenRetryMax(),
pTrkWksConfiguration->GetObjIdIndexReopenLifetime()
);
// Create and register an event that we'll signal when a volume has been unlocked.
// We have to run this on an IO thread so that the logfile oplock and
// ReadDirectoryChanges on the objid index will work.
_heventVolumeToBeReopened = CreateEvent( NULL, FALSE, FALSE, NULL );
if( NULL == _heventVolumeToBeReopened )
{
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't create _hVolumeUnlockEvent") ));
TrkReportInternalError( THIS_FILE_NUMBER, __LINE__, HRESULT_FROM_WIN32(GetLastError()),
NULL );
TrkRaiseWin32Error( GetLastError() );
}
_hRegisterWaitForSingleObjectEx
= TrkRegisterWaitForSingleObjectEx( _heventVolumeToBeReopened, ThreadPoolCallbackFunction,
static_cast<PWorkItem*>(this), INFINITE,
WT_EXECUTEINIOTHREAD | WT_EXECUTELONGFUNCTION );
if( NULL == _hRegisterWaitForSingleObjectEx )
{
TrkLog(( TRKDBG_ERROR, TEXT("Failed RegisterWaitForSingleObjectEx in CVolumeManager::Initialize (%lu)"),
GetLastError() ));
TrkRaiseLastError();
}
// Create a list of CVolume objects, one for each local NTFS5 volume.
InitializeVolumeList( pTrkWksConfiguration, pLogCallback, ssh
#if DBG
, pTunnelTest
#endif
);
if( !pTrkWksConfiguration->_fIsWorkgroup )
{
InitializeDomainObjects();
StartDomainTimers();
}
// Set the event that will get us to open the volume handles on an
// IO thread.
OnVolumeToBeReopened();
}
__finally
{
ssd.UnInitialize();
}
}
void
CVolumeManager::UnInitialize()
{
if( _fInitialized )
{
if( NULL != _hRegisterWaitForSingleObjectEx )
{
if( !TrkUnregisterWait( _hRegisterWaitForSingleObjectEx ))
{
TrkLog(( TRKDBG_ERROR, TEXT("Failed UnregisterWait for CVolumeManager (%lu)"),
GetLastError() ));
}
else
TrkLog(( TRKDBG_VOLUME, TEXT("Unregistered wait CVolumeManager") ));
_hRegisterWaitForSingleObjectEx = NULL;
}
if( NULL != _heventVolumeToBeReopened )
{
CloseHandle( _heventVolumeToBeReopened );
_heventVolumeToBeReopened = NULL;
}
UnInitializeDomainObjects();
_timerObjIdIndexReopen.UnInitialize();
CVolumeNode * pVolumeNode = _pVolumeNodeListHead;
_pVolumeNodeListHead = NULL;
while (pVolumeNode)
{
CVolumeNode * pNext = pVolumeNode->_pNext;
// By this time, all timers and the LPC port are stopped and have unregistered
// with the thread pool. Therefore, there are no other threads running,
// and each of the volume should have a ref count of only 1. For robustness,
// if any volumes have leaked, we release the extra refs here.
while( 0 != pVolumeNode->_pVolume->Release() );
delete pVolumeNode;
pVolumeNode = pNext;
}
_fFrequentTaskHesitation = _fInfrequentTaskHesitation = FALSE;
_csVolumeNodeList.UnInitialize();
_fInitialized = FALSE;
}
}
void
CVolumeManager::DoWork()
{
// One of the volumes has been unlocked. Just try to reopen them all
// (those that aren't in need of opening will noop).
__try
{
// These raise if the service is stopping.
ReopenVolumeHandles();
// If a volume has just been re-created, it may be necessary
// to clean up some object IDs.
CleanUpOids();
}
__except( BreakOnDebuggableException() )
{
TrkLog(( TRKDBG_VOLUME, TEXT("Ignoring exception %08x in CVolumeManager::DoWork"),
GetExceptionCode() ));
}
}
//+----------------------------------------------------------------------------
//
// CVolumeManager::InitializeDomainObjects
//
// Initialize member objects that we don't use in a workgroup. These
// objects can come and go without restarting the service (thus we can
// go between domains or move to/from domain without requiring a reboot).
//
//+----------------------------------------------------------------------------
void
CVolumeManager::InitializeDomainObjects()
{
__try
{
TrkAssert( !_pTrkWksConfiguration->_fIsWorkgroup );
// When this timer fires, we send all our IDs to trksvr so that the entries may
// be touched (entries that are not touched will be GC-ed by trksvr).
// Before actuallying doing the refresh, we sleep a random period, in order to
// avoid too many workstations refreshing at the same time.
_timerRefresh.Initialize( this,
TEXT("NextRefreshTime"), // Name (for persistent timer)
VOLTIMER_REFRESH, // Context ID
// Timer period
_pTrkWksConfiguration->GetRefreshPeriod(),
CNewTimer::RETRY_RANDOMLY, //Retry values
_pTrkWksConfiguration->GetRefreshRetryMin(),
_pTrkWksConfiguration->GetRefreshRetryMax(),
0 ); // No max lifetime
_timerRefresh.SetRecurring();
TrkLog(( TRKDBG_VOLUME, TEXT("Refresh timer: %s"),
(const TCHAR*)CDebugString(_timerRefresh) ));
// The Notify timer is set when we receive a move notification from ntos.
// When it expires we send all unsent notifications up to trksvr.
_timerNotify.Initialize( this,
NULL, // No name (non-persistent timer)
VOLTIMER_NOTIFY, // Context ID
_pTrkWksConfiguration->GetParameter( MOVE_NOTIFY_TIMEOUT_CONFIG ),
CNewTimer::RETRY_WITH_BACKOFF,
_pTrkWksConfiguration->GetParameter( MIN_MOVE_NOTIFY_RETRY_CONFIG ),
_pTrkWksConfiguration->GetParameter( MAX_MOVE_NOTIFY_RETRY_CONFIG ),
_pTrkWksConfiguration->GetParameter( MAX_MOVE_NOTIFY_LIFETIME_CONFIG ) );
// The deletions manager watches for files with object IDs to get
// deleted. When they are, and they're not subsequently tunnelled back,
// a notification is sent to trksvr so that it can remove that birth ID
// from the object move table.
_deletions.Initialize( _pTrkWksConfiguration );
// When this timer fires, we do our ~daily tasks
_timerFrequentTasks.Initialize( this,
TEXT("NextVolFrequentTask"), // Persistent timer
VOLTIMER_FREQUENT, // Context ID
_pTrkWksConfiguration->GetVolFrequentTasksPeriod(),
CNewTimer::NO_RETRY,
0, 0, 0 ); // Ignored for non-retrying timer
// When this timer fires, we do our ~weekly tasks
_timerInfrequentTasks.Initialize( this,
TEXT("NextVolInfrequentTask"),//Persistent timer
VOLTIMER_INFREQUENT, // Context ID
_pTrkWksConfiguration->GetVolInfrequentTasksPeriod(),
CNewTimer::NO_RETRY,
0, 0, 0 ); // Ignored for non-retrying timer
// When this timer fires, we do the initial volume synchronizations.
// This timer is also used to do slow retries. E.g, if we try to send
// a move notification and get a busy error, we retry at this slow rate.
_timerVolumeInit.Initialize( this,
NULL, // No name (non-persistent)
VOLTIMER_INIT, // Context ID
_pTrkWksConfiguration->GetVolInitInitialDelay(),
CNewTimer::RETRY_RANDOMLY,
// Initial retry period
_pTrkWksConfiguration->GetVolInitRetryDelay1(),
// Max retry period
_pTrkWksConfiguration->GetVolInitRetryDelay2(),
_pTrkWksConfiguration->GetVolInitLifetime() );
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
TrkLog(( TRKDBG_ERROR, TEXT("Ignoring exception %08x in CVolumeManager::InitializeDomainObjects"),
GetExceptionCode() ));
}
}
//+----------------------------------------------------------------------------
//
// CVolumeManager::UnInitializeObjects
//
// Free the objects that we don't use in a workgroup. This doesn't require
// stopping the service.
//
//+----------------------------------------------------------------------------
void
CVolumeManager::UnInitializeDomainObjects()
{
__try
{
_timerRefresh.UnInitialize();
_timerNotify.UnInitialize();
_deletions.UnInitialize( );
_timerInfrequentTasks.UnInitialize();
_timerFrequentTasks.UnInitialize();
_timerVolumeInit.UnInitialize();
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
TrkLog(( TRKDBG_ERROR, TEXT("Ignoring exception %08x in CVolumeManager::UnInitializeDomainObjects"),
GetExceptionCode() ));
}
}
//+----------------------------------------------------------------------------
//
// CVolumeManager::StartDomainTimers
//
// Start the timers that we don't use in a workgroup.
//
//+----------------------------------------------------------------------------
void
CVolumeManager::StartDomainTimers()
{
if( !_pTrkWksConfiguration->_fIsWorkgroup )
{
_timerFrequentTasks.SetSingleShot();
TrkLog(( TRKDBG_VOLUME, TEXT("Frequent timer: %s"),
(const TCHAR*)CDebugString(_timerFrequentTasks) ));
_timerInfrequentTasks.SetSingleShot();
TrkLog(( TRKDBG_VOLUME, TEXT("Infrequent timer: %s"),
(const TCHAR*)CDebugString(_timerInfrequentTasks) ));
_timerVolumeInit.SetSingleShot();
TrkLog(( TRKDBG_VOLUME, TEXT("VolInit timer: %s"),
(const TCHAR*)CDebugString(_timerVolumeInit) ));
}
}
//+----------------------------------------------------------------------------
//
// Method: CVolumeManager::RefreshVolumes
//
// Refresh the CVolume objects. This gives them the chance to
// get an updated drive letter, and to delete themselves if the volume
// they represent is now gone.
//
//+----------------------------------------------------------------------------
void
CVolumeManager::RefreshVolumes( PLogCallback *pLogCallback,
SERVICE_STATUS_HANDLE ssh
#if DBG
, CTestSync *pTunnelTest
#endif
)
{
InitializeVolumeList( _pTrkWksConfiguration, pLogCallback, ssh
#if DBG
, pTunnelTest
#endif
);
OnVolumeToBeReopened();
}
//+----------------------------------------------------------------------------
//
// Method: CVolumeManager::InitializeVolumeList
//
// This method initializes the linked list of CVolume objects.
//
//+----------------------------------------------------------------------------
void
CVolumeManager::InitializeVolumeList( const CTrkWksConfiguration *pTrkWksConfiguration,
PLogCallback *pLogCallback,
SERVICE_STATUS_HANDLE ssh
#if DBG
, CTestSync *pTunnelTest
#endif
)
{
ULONG cVolumes = 0;
TCHAR tszVolumeName[ CCH_MAX_VOLUME_NAME + 1 ];
HANDLE hFindVolume = INVALID_HANDLE_VALUE;
CVolumeNode *pVolNode = NULL;
__try
{
// Begin a physical volume enumeration using the mount manager.
// Volume names represent the root in Win32 format, e.g.
// \\?\Volume{8baec120-078b-11d2-824b-000000000000}\
hFindVolume = FindFirstVolume( tszVolumeName, sizeof(tszVolumeName) );
if( INVALID_HANDLE_VALUE == hFindVolume )
{
TrkLog(( TRKDBG_ERROR, TEXT("FindFirstVolume failed") ));
TrkRaiseLastError();
}
// For each local NTFS5 volume, create a CVolume, add it to the linked-list,
// and initialize.
cVolumes = 1;
while( NUM_VOLUMES >= cVolumes )
{
CVolume *pvolOpen = NULL;
TCHAR tszVolumeDeviceName[ MAX_PATH + 1 ];
ULONG cchVolumeName;
TrkLog(( TRKDBG_VOLUME, TEXT("Initializing volume %s"), tszVolumeName ));
// If we already have this volume open, continue on.
cchVolumeName = wcslen(tszVolumeName);
memcpy( tszVolumeDeviceName, tszVolumeName, cchVolumeName*sizeof(TCHAR) );
tszVolumeDeviceName[ cchVolumeName - 1 ] = TEXT('\0');
if( pvolOpen = FindVolume( tszVolumeDeviceName ))
{
TrkLog(( TRKDBG_VOLUME, TEXT("Volume already open" ) ));
pvolOpen->Release();
cVolumes++;
}
// If this isn't an NTFS5 volume, move on.
else if( IsLocalObjectVolume(tszVolumeName) )
{
// Alloc a new node for the volume list
pVolNode = new CVolumeNode;
if (pVolNode == NULL)
{
TrkRaiseException(E_OUTOFMEMORY);
}
// Put a volume into the node
pVolNode->_pVolume = new CVolume();
if (pVolNode->_pVolume == NULL)
{
TrkRaiseException(E_OUTOFMEMORY);
}
// Initialize the volume. Returns true if successful, raises on error.
__try
{
if( pVolNode->_pVolume->Initialize(tszVolumeName, _pTrkWksConfiguration, this,
pLogCallback, &_deletions, ssh
#if DBG
,pTunnelTest
#endif
))
{
cVolumes++;
// Add this volume node (and its associated CVolume) to the linked list.
AddNodeToLinkedList( pVolNode );
pVolNode = NULL;
} // if( pVolNode->_pVolume->Initialize(v, _pTrkWksConfiguration, ...
}
__except( BreakOnDebuggableException() )
{
}
if( NULL != pVolNode )
{
TrkLog(( TRKDBG_VOLUME, TEXT("Volume initialization failed, deleting node") ));
pVolNode->_pVolume->Release();
delete pVolNode;
pVolNode = NULL;
}
} // else if( IsLocalObjectVolume(tszVolumeName) )
#if DBG
else
{
TrkLog(( TRKDBG_VOLUME, TEXT("Skipping volume %s"), tszVolumeName ));
}
#endif
// Move on to the next volume in the system.
if( !FindNextVolume( hFindVolume, tszVolumeName, sizeof(tszVolumeName) ))
{
if( ERROR_NO_MORE_FILES == GetLastError() )
// We've enumerated all of the volumes.
break;
else
{
TrkLog(( TRKDBG_ERROR, TEXT("FindNextVolume failed") ));
TrkRaiseLastError();
}
}
} // while( NUM_VOLUMES >= cVolumes )
}
__finally
{
if( INVALID_HANDLE_VALUE != hFindVolume )
FindVolumeClose( hFindVolume );
if( NULL != pVolNode )
delete pVolNode;
}
}
//+----------------------------------------------------------------------------
//
// Method: CVolumeManager::AddNodeToLinkedList
//
// Adds a CVolumeNode (and its embedded CVolume) to the volume manager's
// linked list.
//
// The CVolumeNode elements in the linked list are kept sorted in
// increasing address order. This is so a CVolumeEnumerator can handle a
// node being deleted while such an enumeration is active.
//
//+----------------------------------------------------------------------------
void
CVolumeManager::AddNodeToLinkedList( CVolumeNode *pVolNode )
{
TrkAssert( _fInitialized );
_csVolumeNodeList.Enter();
__try
{
if( NULL == _pVolumeNodeListHead )
{
pVolNode->_pNext = NULL;
_pVolumeNodeListHead = pVolNode;
}
else if( pVolNode < _pVolumeNodeListHead )
{
pVolNode->_pNext = _pVolumeNodeListHead;
_pVolumeNodeListHead = pVolNode;
}
else
{
CVolumeNode *pNode = _pVolumeNodeListHead;
while( NULL != pNode->_pNext && pNode->_pNext < pVolNode )
pNode = pNode->_pNext;
if( NULL == pNode->_pNext )
{
TrkAssert( pNode < pVolNode );
pNode->_pNext = pVolNode;
pVolNode->_pNext = NULL;
}
else
{
TrkAssert( pNode < pVolNode );
TrkAssert( pNode->_pNext > pVolNode );
pVolNode->_pNext = pNode->_pNext;
pNode->_pNext = pVolNode;
}
}
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
TrkAssert( !TEXT("Unexpected exception in CVolumeManager::AddNodeToLinkedList") );
}
_csVolumeNodeList.Leave();
}
//+----------------------------------------------------------------------------
//
// Method: CVolumeManager::RemoveVolumeFromLinkedList
//
// Removes a volume from the volume manager's linked list.
// The CVolume is Released (which may or may not make it go away, depending
// on ref-counts), and the CVolumeNode is deleted.
//
//+----------------------------------------------------------------------------
void
CVolumeManager::RemoveVolumeFromLinkedList( const CVolume *pvol )
{
CVolumeNode **ppvolnodePrev = NULL;
CVolumeNode *pvolnode = NULL;
TrkAssert( _fInitialized );
_csVolumeNodeList.Enter();
__try
{
pvolnode = _pVolumeNodeListHead;
ppvolnodePrev = &_pVolumeNodeListHead;
while( NULL != pvolnode )
{
if( pvol == pvolnode->_pVolume )
{
TrkLog(( TRKDBG_VOLUME, TEXT("Removing volume %p from the list"), pvolnode->_pVolume ));
CVolumeNode *pvolnodeDel = pvolnode;
*ppvolnodePrev = pvolnode->_pNext;
// Releasing the volume will usually cause it to delete itself, unless
// someone else is holding a ref on it.
pvolnodeDel->_pVolume->Release();
delete pvolnodeDel;
break;
}
else
{
ppvolnodePrev = &pvolnode->_pNext;
pvolnode = pvolnode->_pNext;
}
}
}
__except( BreakOnDebuggableException() )
{
}
_csVolumeNodeList.Leave();
}
void
CVolumeManager::CloseVolumeHandles( HDEVNOTIFY hdnVolume )
{
CVolumeEnumerator volEnum = Enum();
CVolume* vol = volEnum.GetNextVolume();
while (vol != NULL)
{
__try
{
vol->CloseVolumeHandles( hdnVolume );
}
__except( BreakOnDebuggableException() )
{
TrkLog(( TRKDBG_ERROR, TEXT("Ignoring exception in CloseVolumeHandles (%08x)"),
GetExceptionCode() ));
}
vol->Release();
vol = volEnum.GetNextVolume();
}
}
void
CVolumeManager::CleanUpOids()
{
CVolumeEnumerator volEnum = Enum();
CVolume* vol = volEnum.GetNextVolume();
while(vol != NULL)
{
if(vol->IsMarkedForMakeAllOidsReborn())
{
__try
{
if( vol->MakeAllOidsReborn() )
vol->ClearMarkForMakeAllOidsReborn();
}
__except( BreakOnDebuggableException() )
{
TrkLog(( TRKDBG_VOLUME, TEXT("Ignoring exception in CVolumeManager::CleanUpOids") ));
}
}
vol->Release();
vol = volEnum.GetNextVolume();
}
}
//+----------------------------------------------------------------------------
//
// CVolumeManager::SyncVolumes
//
// Give each active volume an opportunity to synchronize with trksvr.
// This allows the volumes to create a new ID, claim an existing ID,
// etc.
//
// Note:: This routine is guaranteed not to raise.
//
//+----------------------------------------------------------------------------
BOOL
CVolumeManager::SyncVolumes( EAggressiveness eAggressiveness,
CFILETIME cftLastDue,
ULONG ulPeriodInSeconds )
{
BOOL fSuccess = FALSE;
BOOL fDoRetry = FALSE;
CAvailableDc adc;
CVolumeEnumerator volEnum;
CVolume* pvol;
BOOL fIsOnlyInstance = FALSE;
__try
{
TRKSVR_SYNC_VOLUME rgSyncVolumes[ NUM_VOLUMES ];
TRKSVR_MESSAGE_UNION Msg;
ULONG cVolumes = 0;
const CVolumeId volNULL;
BOOL fSyncNeeded;
// If there's already a thread doing a SyncVolumes, we don't need to
// do it again simultaneously.
fIsOnlyInstance = BeginSingleInstanceTask( &_cSyncVolumesInProgress );
if( !fIsOnlyInstance )
{
TrkLog(( TRKDBG_VOLUME, TEXT("Skipping SyncVolumes, another instance already in progress") ));
fSuccess = TRUE;
goto Exit;
}
// If we haven't fully initialized the volumes yet, do so now.
ReopenVolumeHandles();
// Start a CVolume enumeration
volEnum = Enum();
pvol = volEnum.GetNextVolume();
// Loop through the enumerated volumes.
// When we're done, cVolumes will show the count of volumes
// that requested to sync with trksvr (could be zero).
while ( NULL != pvol )
{
BOOL fFound = FALSE;
// Add this volume to the array of volumes needing update with trksvr.
// If this particular volume turns out not to need an update, we won't
// increment cVolumes, consequently it will get overwritten on the
// next pass. If this volume does need an update, we'll addref it.
_rgVolumesToUpdate[cVolumes] = pvol;
// Call the volume to load the sync-volume request.
if(TRUE == pvol->LoadSyncVolume(&rgSyncVolumes[cVolumes], eAggressiveness, &fSyncNeeded))
{
// The LoadSyncVolumes request succeeded.
// Does the volume need a sync with trksvr?
if(fSyncNeeded == FALSE)
{
// No, the volume doesn't need a sync. It's
// apparantly neither new nor newly attached.
TrkLog(( TRKDBG_VOLUME | TRKDBG_MOVE,
TEXT("Volume %c is properly ID-ed already"),
'A'+pvol->GetIndex() ));
}
else if(rgSyncVolumes[cVolumes].SyncType == CREATE_VOLUME)
{
// This is a newly-formatted volume.
TrkLog(( TRKDBG_VOLUME | TRKDBG_MOVE,
TEXT("Having a new ID created for volume %c"),
TEXT('A')+pvol->GetIndex() ));
_rgVolumesToUpdate[cVolumes]->AddRef();
cVolumes++;
}
else
{
// The volume is new to this machine
TrkLog(( TRKDBG_VOLUME | TRKDBG_MOVE,
TEXT("Claiming volume %c"),
TEXT('A')+pvol->GetIndex() ));
_rgVolumesToUpdate[cVolumes]->AddRef();
cVolumes++;
}
}
else
{
// The volume needs to be synced but failed for some reason
TrkLog(( TRKDBG_VOLUME | TRKDBG_MOVE,
TEXT("Volume %c can not be synced"),
'A'+pvol->GetIndex() ));
}
// Move on to the next item in the enumeration.
// If this volumes needs an update, it's addref-ed in
// _rgVolumesToUpdate.
pvol->Release();
pvol = volEnum.GetNextVolume();
} // while ( vol != NULL )
// Were there any volumes in need of a sync with trksvr?
if( 0 != cVolumes )
{
// Yes, send the sync_volumes request.
__try
{
HRESULT hr;
// Construct the Msg union
Msg.MessageType = SYNC_VOLUMES;
Msg.Priority = (0 == ulPeriodInSeconds)
? PRI_6
: GetSvrMessagePriority( cftLastDue, ulPeriodInSeconds );
Msg.SyncVolumes.cVolumes = cVolumes;
Msg.SyncVolumes.pVolumes = rgSyncVolumes;
#ifdef VOL_REPL
Msg.SyncVolumes.cChanges = 0;
Msg.SyncVolumes.ppVolumeChanges = NULL;
#endif
// Send the request to trksvr. We pass it under privacy encryption
// because there could be a volume secret in it.
// This will raise if there's an error.
hr = adc.CallAvailableDc(&Msg, PRIVACY_AUTHENTICATION );
// now we've successfully told the DC we should update the
// DcInformed flags
TrkLog((TRKDBG_VOLUME, TEXT("CallAvailableDc returned %d volumes (%08X)"),
Msg.SyncVolumes.cVolumes, hr ));
// See if there were problems and we should do a retry
// (This gets set in the DcCallback method).
if( TRK_S_VOLUME_NOT_SYNCED == hr )
fDoRetry = TRUE;
// Process the responses
for( ULONG v = 0; v < Msg.SyncVolumes.cVolumes; v++ )
{
// If the problem is server-too-busy, have the caller do
// a retry. Otherwise (e.g. quota error) ignore the error.
// E.g. for a quota error, we'll ignore the error, but the
// volume will still be in a not-created state, and we'll
// retry the next time the infrequent timer fires.
if( TRK_E_SERVER_TOO_BUSY == rgSyncVolumes[v].hr )
fDoRetry = TRUE;
if( _rgVolumesToUpdate[v]->UnloadSyncVolume( &rgSyncVolumes[v] )
&&
rgSyncVolumes[v].hr == S_OK
)
{
TrkLog(( TRKDBG_VOLUME | TRKDBG_MOVE,
TEXT("Volume %c successfully synced with server"),
'A'+_rgVolumesToUpdate[v]->GetIndex() ));
}
else
{
TrkLog(( TRKDBG_VOLUME | TRKDBG_MOVE,
TEXT("Couldn't sync vol %c with server (%08x, %s)"),
'A'+_rgVolumesToUpdate[v]->GetIndex(),
rgSyncVolumes[v].hr, GetErrorString(rgSyncVolumes[v].hr) ));
}
} // for( v = 0; v < Msg.SyncVolumes.cVolumes; v++ )
}
__finally
{
for( ULONG v = 0; v < cVolumes; v++ )
{
_rgVolumesToUpdate[v]->Release();
}
}
} // if( 0 != cVolumes )
// If any volids have been changed, make all the existing object IDs
// reborn.
CleanUpOids();
fSuccess = TRUE;
}
__except( BreakOnDebuggableException() )
{
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't sync with server (0x%08x)"), GetExceptionCode() ));
}
if( pvol != NULL )
{
pvol->Release();
}
Exit:
if( fIsOnlyInstance )
EndSingleInstanceTask( &_cSyncVolumesInProgress );
return fSuccess && !fDoRetry;
}
void
CVolumeManager::Append( const CDomainRelativeObjId &droidCurrent,
const CDomainRelativeObjId &droidNew,
const CMachineId &mcidNew,
const CDomainRelativeObjId &droidBirth)
{
CVolumeEnumerator enumerator = Enum();
CVolume* cvolCur = enumerator.GetNextVolume();
BOOL fVolumeFound = FALSE;
__try // __finally
{
while ( cvolCur != NULL )
{
if( cvolCur->GetVolumeId() == droidCurrent.GetVolumeId())
{
fVolumeFound = TRUE;
cvolCur->Append(droidCurrent, droidNew, mcidNew, droidBirth);
break;
}
cvolCur->Release();
cvolCur = enumerator.GetNextVolume();
}
}
__finally
{
if (cvolCur != NULL)
{
cvolCur->Release();
}
}
if( !fVolumeFound )
TrkRaiseNtStatus( STATUS_NO_TRACKING_SERVICE );
}
// search all volumes (and each log) or just the given volume
//
// S_OK || TRK_E_NOT_FOUND || TRK_E_REFERRAL
HRESULT
CVolumeManager::Search( DWORD Restrictions,
const CDomainRelativeObjId & droidBirthLast,
const CDomainRelativeObjId & droidLast,
CDomainRelativeObjId * pdroidBirthNext,
CDomainRelativeObjId * pdroidNext,
CMachineId * pmcidNext,
TCHAR * ptszLocalPath )
{
NTSTATUS status;
HRESULT hr = TRK_E_NOT_FOUND;
CVolumeEnumerator enumerator;
CVolume* pvol = NULL;
const CMachineId mcidLocal( MCID_LOCAL );
BOOL fPotentialFile = FALSE;
CDomainRelativeObjId droidZero;
// Local working stores for what we'll return in pdroidNew & pmcidNew
// if we have a referral.
CDomainRelativeObjId droidBirthNext = droidBirthLast;
CDomainRelativeObjId droidNext = droidLast;
CMachineId mcidNext = mcidLocal;
__try // __finally
{
g_ptrkwks->RaiseIfStopped();
// -------------------------------
// Search the volumes for the file
// -------------------------------
// Search the last volume first, then search all the volumes.
// Thus we'll end up searching droidLast.GetVolumeId() twice.
// This is necessary, though, because there could be multiple
// local volumes with this volid.
if( !(Restrictions & TRK_MEND_DONT_SEARCH_ALL_VOLUMES) )
enumerator = Enum();
if( !(Restrictions & TRK_MEND_DONT_USE_VOLIDS) )
pvol = FindVolume( droidLast.GetVolumeId() );
if( NULL == pvol )
pvol = enumerator.GetNextVolume();
for( ; NULL != pvol; pvol = enumerator.GetNextVolume() )
{
status = FindLocalPath( pvol->GetIndex(), droidLast.GetObjId(),
&droidBirthNext, &ptszLocalPath[2] );
if( NT_SUCCESS(status) )
{
// Is this in the SystemVolumeInformation directory? If so, we'll
// pretend we didn't find it (it's probably in the System Recovery
// directory).
if( IsSystemVolumeInformation( &ptszLocalPath[2] ))
{
TrkLog(( TRKDBG_VOLUME | TRKDBG_MEND,
TEXT("CVolumeManager::Search ignoring %c:%s"),
VolChar(pvol->GetIndex()), &ptszLocalPath[2] ));
}
// Or, is this the correct birth ID (or the caller doesn't want us to check)?
else if( droidBirthLast == droidBirthNext
||
droidBirthLast == droidZero )
{
// Yes. We've found our file and we're done.
// Give the path a drive letter
TrkAssert( -1 != pvol->GetIndex() );
ptszLocalPath[0] = VolChar(pvol->GetIndex());
ptszLocalPath[1] = TEXT(':');
// droidBirthNext is already set by FindLocalPath
droidNext = CDomainRelativeObjId( pvol->GetVolumeId(), droidLast.GetObjId() );
mcidNext = mcidLocal;
TrkLog(( TRKDBG_VOLUME | TRKDBG_MEND, TEXT("CVolumeManager::Search found %s"),
ptszLocalPath ));
hr = S_OK;
fPotentialFile = FALSE;
__leave;
}
// Otherwise, is it the first potential hit?
else if( !fPotentialFile )
{
// We found a file with the right object ID, but the wrong birth ID.
// By rule of law, it's therefore not the right file. However, it could be
// the right file, but was re-born due to a volid change. So we'll keep
// it and let the caller (eventually, the user) decide.
// Give the path a drive letter
ptszLocalPath[0] = VolChar(pvol->GetIndex());
ptszLocalPath[1] = TEXT(':');
// droidBirthNext is already set by FindLocalPath
droidNext = CDomainRelativeObjId( pvol->GetVolumeId(), droidLast.GetObjId() );
mcidNext = mcidLocal;
TrkLog(( TRKDBG_VOLUME | TRKDBG_MEND,
TEXT("CVolumeManager::Search found *potential* %s"),
ptszLocalPath ));
fPotentialFile = TRUE;
}
}
pvol->Release(); pvol = NULL;
} // for( ; NULL != pvol, pvol = enumerator.GetNextVolume() );
enumerator.UnInitialize();
// We didn't find the file on any of the volumes, let's search the logs
// to see if they know where it went.
// ---------------------
// Search the local logs
// ---------------------
if( Restrictions & TRK_MEND_DONT_USE_LOG )
__leave;
// Start by searching the log of the last volume, then enumerate through all the
// volume logs. Again we'll end up searching the last volume twice, because
// there could be dup volids.
if( !(Restrictions & TRK_MEND_DONT_SEARCH_ALL_VOLUMES) )
enumerator = Enum();
if( !(Restrictions & TRK_MEND_DONT_USE_VOLIDS) )
pvol = FindVolume( droidLast.GetVolumeId() );
if( NULL == pvol )
pvol = enumerator.GetNextVolume();
for( ; NULL != pvol; pvol = enumerator.GetNextVolume() )
{
CDomainRelativeObjId droidNextT, droidBirthT;
CMachineId mcidNextT;
if (pvol->Search( droidLast, &droidNextT, &mcidNextT, &droidBirthT ))
{
// We found a match in the log.
TrkLog(( TRKDBG_VOLUME | TRKDBG_MEND, TEXT("Referral on vol %c: %s\n => %s:%s"),
VolChar(pvol->GetVolIndex()),
(const TCHAR*)CDebugString(droidLast.GetObjId()),
(const TCHAR*)CDebugString(mcidNextT),
(const TCHAR*)CDebugString(droidNextT) ));
// If the volid is useful (it's non-NULL and non-local), keep these IDs
// as the best to return.
if( CVolumeId() != droidNextT.GetVolumeId()
&&
( !IsLocal( droidNextT.GetVolumeId() )
||
droidLast.GetObjId() != droidNextT.GetObjId()
||
(Restrictions & TRK_MEND_DONT_SEARCH_ALL_VOLUMES)
)
)
{
hr = TRK_E_REFERRAL;
droidBirthNext = droidBirthLast;
droidNext = droidNextT;
mcidNext = mcidNextT;
fPotentialFile = FALSE;
break;
}
// Or, if the mcid is useful (not this machine), keep the IDs as the
// best to return
else if( CMachineId() != mcidNextT
&&
( mcidLocal != mcidNextT
||
(Restrictions & TRK_MEND_DONT_SEARCH_ALL_VOLUMES)
)
)
{
hr = TRK_E_REFERRAL;
droidBirthNext = droidBirthLast;
mcidNext = mcidNextT;
droidNext = droidNextT;
fPotentialFile = FALSE;
break;
}
}
pvol->Release(); pvol = NULL;
} // for( ; NULL != pvol, pvol = enumerator.GetNextVolume() );
} // __try
_finally
{
if( NULL != pvol )
{
pvol->Release();
pvol = NULL;
}
enumerator.UnInitialize();
}
// If we didn't find the file or a referral, but did find a potential hit,
// return that potential hit.
if( TRK_E_NOT_FOUND == hr && fPotentialFile )
hr = TRK_E_POTENTIAL_FILE_FOUND;
if( SUCCEEDED(hr) || TRK_E_REFERRAL == hr || TRK_E_POTENTIAL_FILE_FOUND == hr )
{
*pdroidBirthNext = droidBirthNext;
*pmcidNext = mcidNext;
*pdroidNext = droidNext;
}
return( hr );
}
PTimerCallback::TimerContinuation
CVolumeManager::Timer( DWORD dwTimerId )
{
PTimerCallback::TimerContinuation continuation = CONTINUE_TIMER;
__try
{
switch ( dwTimerId )
{
case VOLTIMER_OBJID_INDEX_REOPEN:
TrkAssert( _timerObjIdIndexReopen.IsRecurring() );
if( ReopenVolumeHandles() ) // Raises if service is stopped
continuation = BREAK_TIMER;
else
continuation = RETRY_TIMER;
break;
case VOLTIMER_FREQUENT:
case VOLTIMER_INFREQUENT:
case VOLTIMER_INIT:
TrkAssert( !_timerFrequentTasks.IsRecurring() );
TrkAssert( !_timerInfrequentTasks.IsRecurring() );
TrkAssert( !_timerVolumeInit.IsRecurring() );
continuation = OnVolumeTimer( dwTimerId );
break;
// Time to send MoveNotifies to the DC
case VOLTIMER_NOTIFY:
TrkAssert( !_timerNotify.IsRecurring() );
TrkAssert( CNewTimer::RETRY_WITH_BACKOFF == _timerNotify.GetRetryType() );
__try
{
continuation = _pTrkWks->OnMoveBatchTimeout();
}
__except( BreakOnDebuggableException() )
{
// If there was an unexpected error, instead of retrying the
// MoveNotify timer, use the slower (thus DC-friendlier) VolInit
// timer.
TrkLog(( TRKDBG_ERROR, TEXT("Server to busy to receive move notifications, starting VolInit timer") ));
continuation = BREAK_TIMER;
SetVolInitTimer();
}
break;
// Time to refresh the DC with all our active IDs
case VOLTIMER_REFRESH:
// The first time we're called, we hesitate for a random amount of time.
// This hesitation is implemented by setting a flag, then resetting the timer.
// When the timer fires again a little later, we'll do the real work.
if( !_fRefreshHesitation )
{
// This is the first time we've been called.
ULONG ulHesitation = 0;
// Delay a random number of seconds within an interval
_fRefreshHesitation = TRUE;
ulHesitation = QuasiRandomDword()
%
( 1 + _pTrkWksConfiguration->GetRefreshHesitation() );
TrkLog(( TRKDBG_LOG, TEXT("Hesitating %d seconds before executing refresh"), ulHesitation ));
_timerRefresh.ReInitialize( ulHesitation );
continuation = CONTINUE_TIMER;
}
else
{
TrkAssert( _timerRefresh.IsRecurring() );
continuation = _pTrkWks->OnRefreshTimeout(
_timerRefresh.QueryOriginalDueTime(),
_pTrkWksConfiguration->GetRefreshPeriod() );
if( CONTINUE_TIMER == continuation )
{
_timerRefresh.ReInitialize( _pTrkWksConfiguration->GetRefreshPeriod() );
_fRefreshHesitation = FALSE;
}
}
break;
default:
TrkAssert( 0 && "invalid timer id" );
break;
}
}
__except( BreakOnDebuggableException() )
{
TrkLog(( TRKDBG_VOLUME, TEXT("Ignoring exception %08x in CVolumeManager::Timer"),
GetExceptionCode() ));
}
return( continuation );
}
BOOL
CVolumeManager::ReopenVolumeHandles()
{
BOOL fAllOk = TRUE;
// If there's another thread already executing this routine, we'll just skip out.
// This is a reasonable idea, but really shouldn't be necessary; the volumes
// can protect themselves, and one thread should basically noop. However,
// there was an iostress break where these two threads got each other into
// a deadly embrace. This was due to the fact that the win32 thread pool
// has the tendency to put multiple IO work items on the same thread (since
// it queues to IO threads using APCs). So as a workaround, don't run
// this method more than once at a time.
if( !BeginSingleInstanceTask( &_cReopenVolumeHandles ))
{
TrkLog(( TRKDBG_VOLUME, TEXT("Skipping ReopenVolumeHandles, another instance is already in progress") ));
return FALSE;
}
// Get a volume enumerator. After initialization, none of the volumes
// have yet been opened. Ordinarily the Enum method gives us nothing until
// they've been opened once. But this routine is the one that originally does
// the opens, so we need the enumerator to give us everything.
CVolumeEnumerator enumerator = Enum( ENUM_UNOPENED_VOLUMES );
TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("\nAttempting to reopen all volume handles") ));
for( CVolume* pVol = enumerator.GetNextVolume();
pVol != NULL;
pVol = enumerator.GetNextVolume())
{
__try
{
if( !pVol->ReopenVolumeHandles() )
fAllOk = FALSE;
}
__except(BreakOnDebuggableException())
{
fAllOk = FALSE;
}
pVol->Release();
}
// Show that at least an attempt has been made to open all
// volume handles. This is checked in Enum().
_fVolumesHaveBeenOpenedOnce = TRUE;
// Show that we're done with this method.
EndSingleInstanceTask( &_cReopenVolumeHandles );
TrkLog(( TRKDBG_OBJID_DELETIONS,
fAllOk ? TEXT("All volume handles are open") : TEXT("Not all volume handles are open") ));
return( fAllOk );
}
void
CVolumeManager::VolumeDeviceEvent( HDEVNOTIFY hdnVolume, EVolumeDeviceEvent eVolumeDeviceEvent )
{
CVolumeEnumerator enumerator = Enum();
BOOL fAllOk = TRUE;
for( CVolume* pVol = enumerator.GetNextVolume();
pVol != NULL;
pVol = enumerator.GetNextVolume() )
{
__try
{
switch( eVolumeDeviceEvent )
{
case ON_VOLUME_LOCK:
pVol->OnVolumeLock( hdnVolume );
break;
case ON_VOLUME_UNLOCK:
pVol->OnVolumeUnlock( hdnVolume );
break;
case ON_VOLUME_LOCK_FAILED:
pVol->OnVolumeLockFailed( hdnVolume );
break;
case ON_VOLUME_MOUNT:
pVol->OnVolumeMount( hdnVolume );
break;
case ON_VOLUME_DISMOUNT:
pVol->OnVolumeDismount( hdnVolume );
break;
case ON_VOLUME_DISMOUNT_FAILED:
pVol->OnVolumeDismountFailed( hdnVolume );
break;
default:
TrkLog(( TRKDBG_ERROR, TEXT("Invalid event to OnVolumeDeviceEvent (%d)"),
eVolumeDeviceEvent ));
TrkAssert( !TEXT("Invalid event to OnVolumeDeviceEvent") );
break;
} // switch( eVolumeDeviceEvent )
}
__except(BreakOnDebuggableException())
{
}
pVol->Release();
}
return;
}
//+----------------------------------------------------------------------------
//
// CVolumeManager::OnEntriesAvailable
//
// The CLog calls this routine when it has new data available
// for us to read. We don't read it right away, but start the Notify timer.
// When it goes off, we'll upload all notifications to the DC that haven't
// yet been sent.
//
//+----------------------------------------------------------------------------
void
CVolumeManager::OnEntriesAvailable()
{
if( !_pTrkWksConfiguration->_fIsWorkgroup )
{
_timerNotify.SetSingleShot();
TrkLog(( TRKDBG_MOVE | TRKDBG_WKS,
TEXT("log called CVolumeManager::OnEntriesAvailable(), %s"),
(const TCHAR*)CDebugString(_timerNotify) ));
}
}
//+----------------------------------------------------------------------------
//
// CVolumeManager::ForceVolumeClaims
//
// Put all of the volumes in the not-owned state so that they will
// try to do a claim.
//
//+----------------------------------------------------------------------------
void
CVolumeManager::ForceVolumeClaims()
{
CVolumeEnumerator enumerator = Enum();
TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("Force volume claims") ));
for( CVolume* pVol = enumerator.GetNextVolume();
pVol != NULL;
pVol = enumerator.GetNextVolume())
{
__try
{
pVol->SetState(CVolume::VOL_STATE_NOTOWNED);
}
__except(BreakOnDebuggableException())
{
}
pVol->Release();
}
// Do a SyncVolumes so that the volumes can all send up a
// volume-claim request. If it fails, just start the volinit
// timer to have it called again later.
if( !SyncVolumes( AGGRESSIVE )) // Doesn't raise
SetVolInitTimer();
}
PTimerCallback::TimerContinuation
CVolumeManager::OnVolumeTimer( DWORD dwTimerId )
{
ULONG ulTimerResetPeriod = 0;
BOOL fTimerRetry = FALSE;
CNewTimer * ptimer;
PTimerCallback::TimerContinuation continuation = CONTINUE_TIMER;
__try
{
switch( static_cast<VOLTIMERID>(dwTimerId) )
{
// Initialization tasks
// This timer is started during initialization, and then usually stops after
// one iteration. It can be restarted, however, if a MoveNotify gets a
// TRK_E_SERVER_TOO_BUSY error.
case VOLTIMER_INIT:
TrkLog(( TRKDBG_VOLUME, TEXT("VolInit timer has fired") ));
ptimer = &_timerVolumeInit;
TrkAssert( CNewTimer::RETRY_RANDOMLY == ptimer->GetRetryType() );
TrkAssert( !ptimer->IsRecurring() );
// Create/claim volumes as necessary.
fTimerRetry = TRUE;
if( SyncVolumes( PASSIVE,
_timerVolumeInit.QueryOriginalDueTime(),
_timerVolumeInit.QueryPeriodInSeconds() )) // Doesn't raise
fTimerRetry = FALSE;
// If we have a new volid, make the existing object IDs reborn.
CleanUpOids();
// Give each of the volumes an opportunity to upload any pending
// MoveNotifies.
// BUGBUG: Move the MoveBatchTimeout controlling code into CVolumeManager,
// so that all such control is in one place.
__try
{
continuation = g_ptrkwks->OnMoveBatchTimeout();
}
__except( BreakOnDebuggableException() )
{
TrkAssert( TRK_E_SERVER_TOO_BUSY == GetExceptionCode() );
TrkLog(( TRKDBG_ERROR, TEXT("VolInit timer caught %08x during MoveNotify"), GetExceptionCode() ));
fTimerRetry = TRUE;
}
break;
// Frequent tasks (i.e. ~daily)
case VOLTIMER_FREQUENT:
ptimer = &_timerFrequentTasks;
if( _fFrequentTaskHesitation )
{
TrkLog(( TRKDBG_VOLUME, TEXT("Executing frequent tasks") ));
_fFrequentTaskHesitation = FALSE;
ulTimerResetPeriod = _pTrkWksConfiguration->GetVolFrequentTasksPeriod() + 1;
SyncVolumes( PASSIVE ); // Doesn't raise
ReopenVolumeHandles();
}
else
{
// Delay a random number of seconds within an interval
_fFrequentTaskHesitation = TRUE;
ulTimerResetPeriod = QuasiRandomDword()
%
(1+_pTrkWksConfiguration->GetVolPeriodicTasksHesitation());
ulTimerResetPeriod++;
TrkLog(( TRKDBG_VOLUME, TEXT("Hesitating %d seconds before executing frequent tasks"), ulTimerResetPeriod ));
}
break;
// Infrequent tasks (i.e. ~weekly)
case VOLTIMER_INFREQUENT:
ptimer = &_timerInfrequentTasks;
if( _fInfrequentTaskHesitation )
{
TrkLog(( TRKDBG_VOLUME, TEXT("Executing infrequent tasks") ));
_fInfrequentTaskHesitation = FALSE;
ulTimerResetPeriod = _pTrkWksConfiguration->GetVolInfrequentTasksPeriod() + 1;
CheckSequenceNumbers();
// Be aggressive about the sync; if we have not-created volumes, try
// to create them again even if the last time we got a vol-quota-exceeded
// error.
SyncVolumes( AGGRESSIVE ); // Doesn't raise
// Give each of the volumes an opportunity to upload any pending
// MoveNotifies. Again, be aggressive about it in the face of previous
// quota errors.
g_ptrkwks->OnMoveBatchTimeout( AGGRESSIVE );
}
else
{
// Delay a random number of seconds within an interval
_fInfrequentTaskHesitation = TRUE;
ulTimerResetPeriod = QuasiRandomDword()
%
(1+_pTrkWksConfiguration->GetVolPeriodicTasksHesitation());
ulTimerResetPeriod++;
TrkLog(( TRKDBG_VOLUME, TEXT("Hesitating %d seconds before executing infrequent tasks"), ulTimerResetPeriod ));
}
break;
default:
TrkAssert( FALSE && TEXT("Invalid timerID in CVolumeManager::Timer") );
break;
} // switch
}
__except( BreakOnDebuggableException() )
{
TrkLog(( TRKDBG_ERROR, TEXT("Ignoring exception in CVolumeManager::OnVolumeTimer %08x"), GetExceptionCode() ));
}
if( fTimerRetry )
continuation = RETRY_TIMER;
else if( 0 != ulTimerResetPeriod )
{
ptimer->ReInitialize( ulTimerResetPeriod );
ptimer->SetSingleShot();
continuation = CONTINUE_TIMER;
}
return( continuation );
}
ULONG
CVolumeManager::GetVolumeIds( CVolumeId * pVolumeIds, ULONG cMax )
{
CVolumeEnumerator enumerator = Enum();
CVolume* pvol = enumerator.GetNextVolume();
ULONG cVolumes;
CVolumeId volidZero;
__try
{
for( cVolumes = 0;
cVolumes < cMax && NULL != pvol;
pvol = enumerator.GetNextVolume()
)
{
*pVolumeIds = pvol->GetVolumeId();
if (*pVolumeIds != volidZero)
{
pVolumeIds ++;
cVolumes ++;
}
pvol->Release();
pvol = NULL;
}
}
__finally
{
enumerator.UnInitialize();
if(pvol)
{
pvol->Release();
pvol = NULL;
}
}
return(cVolumes);
}
HRESULT
CVolumeManager::OnRestore()
{
return( E_NOTIMPL );
#if 0
CVolumeEnumerator enumerator = Enum();
CVolume* pvol = enumerator.GetNextVolume();
HRESULT hr = S_OK;
HRESULT hrRet = S_OK;
__try
{
for(; NULL != pvol; pvol = enumerator.GetNextVolume())
{
hr = pvol->OnRestore();
if(hr != S_OK && hrRet == S_OK)
// Return the hr from the first failed volume.
{
hrRet = hr;
}
pvol->Release();
pvol = NULL;
}
}
__finally
{
enumerator.UnInitialize();
if(pvol)
{
pvol->Release();
pvol = NULL;
}
}
return hr;
#endif // #if 0
}
BOOL
CVolumeManager::CheckSequenceNumbers()
{
BOOL fSuccess = FALSE;
HRESULT hr = S_OK;
CAvailableDc adc;
CVolume* rgVolsToCheck[ NUM_VOLUMES ];
ULONG cVolumes = 0;
__try
{
TRKSVR_SYNC_VOLUME rgQueryVolumes[ NUM_VOLUMES ];
ULONG v;
const CVolumeId volNULL;
CVolumeEnumerator enumerator = Enum();
CVolume* cvolCur = enumerator.GetNextVolume();
for(; cvolCur != NULL; cvolCur = enumerator.GetNextVolume())
{
rgVolsToCheck[cVolumes] = cvolCur;
if(cvolCur->LoadQueryVolume(&rgQueryVolumes[cVolumes]))
{
cVolumes++;
}
else
{
cvolCur->Release();
}
}
if( 0 != cVolumes )
{
TrkLog(( TRKDBG_VOLUME | TRKDBG_MOVE, TEXT("Verifying sequence numbers on %d volumes"), cVolumes ));
_pTrkWks->CallDcSyncVolumes(cVolumes, rgQueryVolumes);
for( v = 0; v < cVolumes; v++ )
{
if( rgVolsToCheck[v]->UnloadQueryVolume( &rgQueryVolumes[v] )
&&
S_OK == rgQueryVolumes[ v ].hr )
{
TrkLog(( TRKDBG_VOLUME | TRKDBG_MOVE,
TEXT("Seq number for volume %c should be %d"),
'A'+rgVolsToCheck[v]->GetIndex(),
rgQueryVolumes[v].seq ));
}
else
{
TrkLog(( TRKDBG_ERROR,
TEXT("Couldn't verify the seq number on vol %c"),
'A'+rgVolsToCheck[v]->GetIndex() ));
}
} // for( v = 0; v < cVolumes; v++ )
fSuccess = TRUE;
} // if( cVolumes != 0 )
}
__except( BreakOnDebuggableException() )
{
TrkLog(( TRKDBG_VOLUME, TEXT("Couldn't check sequence numbers against server"), GetExceptionCode() ));
}
for( ULONG v = 0; v < cVolumes; v++ )
{
rgVolsToCheck[v]->Release();
} // for( v = 0; v < cVolumes; v++ )
return( fSuccess );
}
void
CVolumeManager::SetReopenVolumeHandlesTimer()
{
CFILETIME ft;
// Start the timer (if it's not already running).
_timerObjIdIndexReopen.SetRecurring();
TrkLog(( TRKDBG_VOLUME, TEXT("ReOpen timer: %s"),
(const TCHAR*)CDebugString(_timerObjIdIndexReopen) ));
}
CVolumeEnumerator
CVolumeManager::Enum( EEnumType eEnumType )
{
CVolumeEnumerator volenum;
if( _fVolumesHaveBeenOpenedOnce
||
_fInitialized
&&
ENUM_UNOPENED_VOLUMES == eEnumType )
{
volenum = CVolumeEnumerator( &_pVolumeNodeListHead, &_csVolumeNodeList );
}
return( volenum );
}
CVolume *
CVolumeManager::FindVolume( const CVolumeId &vol )
{
CVolumeEnumerator enumerator = Enum();
CVolume *pvol = enumerator.GetNextVolume();
for(; pvol != NULL; pvol = enumerator.GetNextVolume())
{
if( pvol->GetVolumeId() == vol )
{
break;
}
pvol->Release();
} // for(; pvol != NULL; pvol = enumerator.GetNextVolume())
return( pvol );
}
//+----------------------------------------------------------------------------
//
// CVolumeManager::FlushAllVolumes
//
// Flush all of the volumes.
//
//+----------------------------------------------------------------------------
void
CVolumeManager::FlushAllVolumes( BOOL fServiceShutdown )
{
CVolumeEnumerator enumerator = Enum();
CVolume *pvol = enumerator.GetNextVolume();
for(; pvol != NULL; pvol = enumerator.GetNextVolume())
{
__try
{
pvol->Flush( fServiceShutdown );
}
__except( BreakOnDebuggableException() )
{
}
pvol->Release();
}
}
//+----------------------------------------------------------------------------
//
// CVolumeManager::FindVolume
//
// Find a volume in the linked-list, given it's device name,
// and return its CVolume*.
//
//+----------------------------------------------------------------------------
CVolume *
CVolumeManager::FindVolume( const TCHAR *ptszVolumeDeviceName )
{
CVolumeEnumerator enumerator = Enum();
CVolume *pvol = enumerator.GetNextVolume();
for(; pvol != NULL; pvol = enumerator.GetNextVolume())
{
if( 0 == _tcscmp( pvol->GetVolumeDeviceName(), ptszVolumeDeviceName ))
{
break;
}
pvol->Release();
} // for(; pvol != NULL; pvol = enumerator.GetNextVolume())
return( pvol );
}
//+----------------------------------------------------------------------------
//
// CVolumeManager::IsDuplicatevolId
//
// Check to see if there is a volume, aside from the caller, that
// has a particular volume ID. This should never happen, so this
// method allows a volume to check for it and respond appropriately.
//
// When checking another volume's volid, we can't take its lock.
// See the description in CVolume::GetVolumeId.
//
//+----------------------------------------------------------------------------
CVolume *
CVolumeManager::IsDuplicateVolId( CVolume *pvolCheck, const CVolumeId &volid )
{
CVolumeNode *pVolNode = NULL;
CVolume *pvol = NULL;
_csVolumeNodeList.Enter();
__try
{
pVolNode = _pVolumeNodeListHead;
while( NULL != pVolNode )
{
if( NULL != pVolNode->_pVolume
&&
volid == pVolNode->_pVolume->GetVolumeId() // Doesn't take lock.
&&
pvolCheck != pVolNode->_pVolume )
{
pvol = pVolNode->_pVolume;
pvol->AddRef();
__leave;
}
pVolNode = pVolNode->_pNext;
TrkAssert( pVolNode != _pVolumeNodeListHead );
}
}
__except( BreakOnDebuggableException() )
{
TrkLog(( TRKDBG_WARNING, TEXT("Ignoring exception in IsDuplicateVolId") ));
}
_csVolumeNodeList.Leave();
return pvol;
}
//+----------------------------------------------------------------------------
//
// CVolumeManager::IsLocal
//
// Determine if a given volume ID represents a local volume. Note that
// if it's not in the linked list of volumes, we'll return false, though
// the volume may in fact exist on the system (since we don't respond
// to new volumes after service start).
//
//+----------------------------------------------------------------------------
BOOL
CVolumeManager::IsLocal( const CVolumeId &vol )
{
CVolume *pvol = FindVolume( vol );
if( NULL == pvol )
return FALSE;
else
{
pvol->Release();
return( TRUE );
}
}
//+----------------------------------------------------------------------------
//
// CVolumeManager::Seek
//
// Seek the specified volume's log to the specified sequence number.
//
//+----------------------------------------------------------------------------
void
CVolumeManager::Seek( CVolumeId vol, SequenceNumber seq )
{
CVolumeEnumerator enumerator = Enum();
CVolume* cvolCur = enumerator.GetNextVolume();
for(; cvolCur != NULL; cvolCur = enumerator.GetNextVolume())
{
if( cvolCur->GetVolumeId() == vol )
{
cvolCur->Seek( seq );
cvolCur->Release();
return;
}
cvolCur->Release();
} // for( ULONG i = 0; i < 26; i++ )
}
//+----------------------------------------------------------------------------
//
// CVolumeEnumerator::GetNextVolume
//
// Get the next CVolume* in the enumeration.
//
//+----------------------------------------------------------------------------
CVolume *
CVolumeEnumerator::GetNextVolume()
{
CVolume *pVol = NULL;
if( NULL == _ppVolumeNodeListHead )
return( NULL );
TrkAssert( NULL != _pcs );
_pcs->Enter();
__try
{
if( NULL == *_ppVolumeNodeListHead )
{
// There are no volumes in the list
pVol = NULL;
}
else if( NULL == _pVolNodeLast )
{
// This is a new enumeration. Pass back the first volume
pVol = (*_ppVolumeNodeListHead)->_pVolume;
_pVolNodeLast = *_ppVolumeNodeListHead;
}
else
{
// Find the next volume in the list, the one that's
// just beyond _pVolNodeLast.
// If we terminate this while loop because pVolNode goes to
// NULL, it means that there are no more volumes left to
// enumerate.
CVolumeNode *pVolNode = *_ppVolumeNodeListHead;
while( NULL != pVolNode )
{
if( pVolNode > _pVolNodeLast )
{
pVol = pVolNode->_pVolume;
_pVolNodeLast = pVolNode;
break;
}
pVolNode = pVolNode->_pNext;
}
}
if( NULL != pVol )
pVol->AddRef();
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
TrkAssert( !TEXT("Unexpected exception in GetNextVolume") );
}
_pcs->Leave();
return( pVol );
}
//+----------------------------------------------------------------------------
//
// CVolumeManager::DcCallback
// StubLnkSvrMessageCallback
//
// When we RPC to trksvr to do a create volume (in the SyncVolumes method),
// trksvr does an RPC callback on that connection to StubLnkSvrMessageCallback,
// which in turn calls the DcCallback method. This was done so that
// we can verify that the volid actually gets to the volume before taking
// the hit of writing it into the DS. (At one point, the request to trksvr
// was being received, the entry was being put into the DS, but then the
// response back to trkwks was getting an RPC error, so trkwks would retry
// the create, etc.
//
//+----------------------------------------------------------------------------
HRESULT CVolumeManager::DcCallback(ULONG cVolumes, TRKSVR_SYNC_VOLUME* rgVolumes)
{
HRESULT hr = S_OK;
BOOL fSuccess = TRUE;
TrkLog((TRKDBG_VOLUME, TEXT("Dc Callback with %d volumes"), cVolumes ));
for( ULONG v = 0; v < cVolumes; v++ )
{
if( _rgVolumesToUpdate[v]->UnloadSyncVolume( &rgVolumes[v] )
&&
rgVolumes[v].hr == S_OK )
{
TrkLog(( TRKDBG_VOLUME | TRKDBG_MOVE,
TEXT("Volume %c successfully synced with server"),
'A'+_rgVolumesToUpdate[v]->GetIndex() ));
}
else
{
fSuccess = FALSE;
TrkLog(( TRKDBG_ERROR | TRKDBG_MOVE,
TEXT("Couldn't sync vol %c with server (%08x, %s)"),
'A'+_rgVolumesToUpdate[v]->GetIndex(),
rgVolumes[v].hr, GetErrorString(rgVolumes[v].hr) ));
}
} // for( v = 0; v < cVolumes; v++ )
if( S_OK == hr )
return fSuccess ? S_OK : TRK_S_VOLUME_NOT_SYNCED;
else
return hr;
}
// DC callback function. When calling CAvailableDc::CallAvailableDc, DC will callback to the
// trkwks service to set the volume ids on the volumes.
HRESULT StubLnkSvrMessageCallback(TRKSVR_MESSAGE_UNION* pMsg)
{
return g_ptrkwks->_volumes.DcCallback(pMsg->SyncVolumes.cVolumes, pMsg->SyncVolumes.pVolumes);
}