// Copyright (c) 1996-1999 Microsoft Corporation //+============================================================================ // // delmgr.cxx // // Implementation of CDeletionsManager, which watches file deletes on // NTFS5 to determine if a link source delete notification should be // sent to trksvr. // //+============================================================================ #include "pch.cxx" #pragma hdrstop #include "trkwks.hxx" //+---------------------------------------------------------------------------- // // CDeletionsManager::Initialize // // Initialize the delete-notify timer. // //+---------------------------------------------------------------------------- void CDeletionsManager::Initialize( const CTrkWksConfiguration *pconfigWks ) { _pLatestDeletions = NULL; _pOldestDeletions = NULL; _cLatestDeletions = 0; _pconfigWks = pconfigWks; _csDeletions.Initialize(); _fInitialized = TRUE; _timerDeletions.Initialize( this, NULL, // No name (non-persistent timer) // Context ID DELTIMER_DELETE_NOTIFY, pconfigWks->GetDeleteNotifyTimeout(), CNewTimer::NO_RETRY, 0, 0, 0 ); // Ignored for non-retrying timer } //+---------------------------------------------------------------------------- // // CDeleteionsManager::UnInitialize // // Free the lists of deletions, and free the timer. // //+---------------------------------------------------------------------------- void CDeletionsManager::UnInitialize() { if (_fInitialized) { _fInitialized = FALSE; _timerDeletions.Cancel(); FreeDeletions(); _pOldestDeletions = _pLatestDeletions; FreeDeletions(); _timerDeletions.UnInitialize(); _csDeletions.UnInitialize(); } } //+---------------------------------------------------------------------------- // // CDeletionsManager::NotifyAddOrDelete // // This method is called (by the CObjIdIndexChangeNotifier) when an object // ID has been added, or been removed by a delete (not removed by a move). // When an objid is deleted, we add the birth ID to a list, and after a time // we'll send it up to trksvr. When an objid is added, we see if its birth // ID is in our list of delete-notifies (this happens after tunnelling), and // remove it if it is. // // Since we hold on to deletes for at least 5 minutes (configurable), and // the tunnelling windows is 15 seconds (though also configurable), we don't // worry about a add (tunnel) message coming in after we've already sent // a delete notification to trksvr. // //+---------------------------------------------------------------------------- void CDeletionsManager::NotifyAddOrDelete( ULONG Action, const CDomainRelativeObjId & droid ) { // Note: this function must not access _pOldestDeletions DROID_LIST_ELEMENT * Element; // Ignore if we're uninitialized (this will happen if the machine is // in a workgroup). if( !_fInitialized ) return; // If bit 0 if the volume id is clear, then the file hasn't moved off the // volume and there's nothing we need do. if (! droid.GetVolumeId().GetUserBitState()) { if( FILE_ACTION_REMOVED_BY_DELETE == Action ) { TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("Ignoring droid=%s because bit 0 is clear\n"), (const TCHAR*)CDebugString(droid) )); } return; } // Take the lock and see if we care about the action _csDeletions.Enter(); __try { // Object ID Removed by DeleteFile if( FILE_ACTION_REMOVED_BY_DELETE == Action ) { // Add this droid to the list of delete-notifies we need to send to the DC. if( _pconfigWks->GetParameter( IGNORE_MOVES_AND_DELETES_CONFIG ) ) { TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("Ignoring delete due to configuration") )); } else if( _cLatestDeletions < _pconfigWks->GetParameter( MAX_DELETE_NOTIFY_QUEUE_CONFIG )) { Element = new DROID_LIST_ELEMENT; if( Element ) { _cLatestDeletions++; Element->droid = droid; Element->droid.GetVolumeId().Normalize(); // Insert the deletion onto the head of the first list. if (_pLatestDeletions == NULL) { _timerDeletions.SetRecurring( ); TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("DeleteNotify Timer: %s"), (const TCHAR*)CDebugString(_timerDeletions) )); } Element->pNext = _pLatestDeletions; _pLatestDeletions = Element; TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("Inserted droid=%s into DROID_LIST_ELEMENT at %08x"), (const TCHAR*)CDebugString(droid), Element)); } } #if DBG else { TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("Ignoring delete-notify due to max queue size") )); } #endif } // if( FILE_ACTION_REMOVED_BY_DELETE == Action ) else if( FILE_ACTION_ADDED == Action ) { // See if this droid is in our delete-list. When a document does a safe-save, // the first thing we see is the file getting deleted, and we add the droid // to our list of delete-notifies to send to the DC. But when the ID gets // tunnelled, we need to remove it from that list. BOOL fDone = FALSE; DROID_LIST_ELEMENT **ppScan; CDomainRelativeObjId droidBirth = droid; droidBirth.GetVolumeId().Normalize(); // Start with the first list ppScan = &_pLatestDeletions; for( int i = 0; i < 2; i++ ) { while( NULL != *ppScan ) { if( (*ppScan)->droid == droidBirth ) { // This is the entry we're looking for. // Remove it. DROID_LIST_ELEMENT *pDel = *ppScan; *ppScan = (*ppScan)->pNext; delete pDel; fDone = TRUE; TrkLog((TRKDBG_OBJID_DELETIONS, TEXT("Removed droid=%s delete-notify list"), (const TCHAR*)CDebugString(droid) )); break; // while } // Move to the next element in this list. ppScan = &(*ppScan)->pNext; } // If we're done then break out. Otherwise, move on // to the other linked list. if( fDone ) break; else ppScan = &_pOldestDeletions; } // for( int i = 0; i < 2; i++ ) } // else if( FILE_ACTION_ADDED == Action ) } __finally { _csDeletions.Leave(); } } //+---------------------------------------------------------------------------- // // CDeletionsManager::OnDeleteNotifyTimeout // // Process the delete notifications in the linked list at _pOldestDeletions. // The notifications from this list are sent to trksvr, then the entries // in that list are freed, and the entries from _pLatestDeletions are // moved to _pOldestDeletions. // // Note: This method assumes it is non-reentrant, since it is only // called when the single delete notify timer fires. // //+---------------------------------------------------------------------------- PTimerCallback::TimerContinuation CDeletionsManager::OnDeleteNotifyTimeout() { CAvailableDc adc; PTimerCallback::TimerContinuation continuation = CONTINUE_TIMER; // Keep track of the number of attempts to send a single // batch. static cAttempts = 0; TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("Delete notify timeout") )); __try // __finally { // Are there deletions that need to be sent to trksvr? if( _pOldestDeletions != NULL && !_pconfigWks->_fIsWorkgroup ) { TRKSVR_MESSAGE_UNION Msg; CDomainRelativeObjId adroidBirth[ 32 ]; DROID_LIST_ELEMENT * pScan; ULONG cdroidBirth; pScan = _pOldestDeletions; // Send the delete notifications in batches. do { g_ptrkwks->RaiseIfStopped(); // // Count the number of list elements and put into the // array format for the RPC call. // for ( cdroidBirth = 0; pScan != NULL && cdroidBirth < ELEMENTS(adroidBirth); cdroidBirth++, pScan = pScan->pNext ) { adroidBirth[cdroidBirth] = pScan->droid; TrkAssert( pScan != pScan->pNext && pScan->pNext != _pOldestDeletions ); } if( cdroidBirth != 0 ) { // As a sanity check, make sure we don't stick on a single // batch forever. if( cAttempts >= _pconfigWks->GetParameter( MAX_DELETE_NOTIFY_ATTEMPTS_CONFIG ) ) { TrkLog(( TRKDBG_WARNING, TEXT("Aborting delete-notify list") )); break; } // Send the delete notifications to trksvr Msg.MessageType = DELETE_NOTIFY; Msg.Priority = PRI_5; Msg.Delete.cVolumes = 0; Msg.Delete.pVolumes = NULL; Msg.Delete.adroidBirth = adroidBirth; Msg.Delete.cdroidBirth = cdroidBirth; // if the DC is down -> exception (really can't do anything about it) cAttempts++; TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("Sending %d delete notifications"), cdroidBirth )); adc.CallAvailableDc(&Msg); TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("Sent %d delete notifications"), cdroidBirth )); // Free the entries that we've sent so far. FreeDeletions( pScan ); } // Reset the per-batch retry count. cAttempts = 0; } while (pScan != NULL); } } __finally { if( AbnormalTermination() ) TrkLog(( TRKDBG_ERROR, TEXT("Couldn't send deletions") )); else { // Free the old deletions, and swap the list so that the // "new" deletions become "old" (to be sent the next time // this timer fires). FreeDeletions(); cAttempts = 0; _csDeletions.Enter(); if (_pLatestDeletions == NULL) { // // If there is nothing to notify, cancel the timer // continuation = BREAK_TIMER; } _pOldestDeletions = _pLatestDeletions; _pLatestDeletions = NULL; _cLatestDeletions = 0; _csDeletions.Leave(); } adc.UnInitialize(); } return( continuation ); } //+---------------------------------------------------------------------------- // // CDeletionsManager::FreeDeletions // // Free the delete notifications from the "old" list // up to (but not including) pStop or the end. // //+---------------------------------------------------------------------------- void CDeletionsManager::FreeDeletions( DROID_LIST_ELEMENT *pStop ) { DROID_LIST_ELEMENT * pScan = _pOldestDeletions; while (pScan && pStop != pScan) { DROID_LIST_ELEMENT * pNext = pScan->pNext; delete pScan; pScan = pNext; } _pOldestDeletions = pStop; } //+---------------------------------------------------------------------------- // // CDeletionsManager::Timer // // Called when the delete timer fires. This triggers us to send the // notifications from the old list, and then to move the "new" items to // the old list. // //+---------------------------------------------------------------------------- PTimerCallback::TimerContinuation CDeletionsManager::Timer( ULONG ulTimerId ) { TimerContinuation continuation = CONTINUE_TIMER; TrkAssert( ulTimerId == DELTIMER_DELETE_NOTIFY ); TrkAssert( _timerDeletions.IsRecurring() ); __try { // This will raise if service is stopping continuation = OnDeleteNotifyTimeout(); } __except( EXCEPTION_EXECUTE_HANDLER ) { } return( continuation ); }