2128 lines
67 KiB
C++
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);
|
|
}
|