// Copyright (c) 1996-1999 Microsoft Corporation //+============================================================================ // // volmgr.cxx // // This file implements the CVolumeManager class. That class maintains // a list of CVolume objects. // //+============================================================================ #include #pragma hdrstop #include "trkwks.hxx" #include #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(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(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); }