// Copyright (c) 1996-1999 Microsoft Corporation //+============================================================================ // // vol.cxx // // This file implements the CVolume class. This class maintains all activities // for a volume, such as the the log, log file, and deletions manager classes. // //+============================================================================ #include #pragma hdrstop #include "trkwks.hxx" #include #define THIS_FILE_NUMBER VOL_CXX_FILE_NO //+---------------------------------------------------------------------------- // // CVolume::AddRef // //+---------------------------------------------------------------------------- ULONG CVolume::AddRef() { long cNew; cNew = InterlockedIncrement( &_lRef ); //TrkLog(( TRKDBG_VOLUME, TEXT("+++ Vol %c: refs => %d"), VolChar(_iVol), cNew )); return( cNew ); } //+---------------------------------------------------------------------------- // // CVolume::Release // //+---------------------------------------------------------------------------- ULONG CVolume::Release() { long cNew; cNew = InterlockedDecrement( &_lRef ); //TrkLog(( TRKDBG_VOLUME, TEXT("--- Vol %c: refs => %d"), VolChar(_iVol), cNew )); if( 0 == cNew ) delete this; return( cNew >= 0 ? cNew : 0 ); } //+---------------------------------------------------------------------------- // // CVolume::Initialize // // Initialize CVolume and open a handle to the volume itself, but nothing // more (e.g. don't open the log or verify volume IDs). The remainder of // the initialization will occur later the first time ReopenVolumeHandles // is called. This gets that heavy IO work out of the service initialization // path. // //+---------------------------------------------------------------------------- BOOL CVolume::Initialize( TCHAR *ptszVolumeName, const CTrkWksConfiguration * pTrkWksConfiguration, CVolumeManager *pVolMgr, PLogCallback * pLogCallback, PObjIdIndexChangedCallback * pObjIdIndexChangedCallback, SERVICE_STATUS_HANDLE ssh #if DBG , CTestSync * pTunnelTest #endif ) { HANDLE hFile = NULL; // const CVolumeId volNULL; // CVolumeId volidVolume; NTSTATUS status; BOOL fSuccess = FALSE; _iVol = -1; memset( &_volinfo, 0, sizeof(_volinfo) ); // Save the volume name, without the trailing whack // Volume names are in the form \\?\Volume{guid}\ _tcscpy( _tszVolumeDeviceName, ptszVolumeName ); TrkAssert( TEXT('\\') == _tszVolumeDeviceName[ _tcslen(_tszVolumeDeviceName)-1 ] ); _tszVolumeDeviceName[ _tcslen(_tszVolumeDeviceName)-1 ] = TEXT('\0'); // Save the inputs _pTrkWksConfiguration = pTrkWksConfiguration; _pVolMgr = pVolMgr; _pLogCallback = pLogCallback; _ssh = ssh; _hdnVolumeLock = NULL; _fVolumeDismounted = _fVolumeLocked = FALSE; IFDBG( _pTunnelTest = pTunnelTest; ) __try // __except { // Create critical sections _csVolume.Initialize(); _csHandles.Initialize(); _fInitialized = TRUE; Lock(); __try // __finally { _VolQuotaReached.Initialize(); // Open the volume (not a directory in the volume, but the volume itself). // We'll use this to do relative-opens by object ID status = OpenVolume( _tszVolumeDeviceName, &_hVolume ); if (!NT_SUCCESS(status)) TrkRaiseNtStatus(status); // Initialize, but don't start, the objid index change notifier. When started, // this will watch for adds/deletes/tunnels/etc. in the index. _ObjIdIndexChangeNotifier.Initialize( _tszVolumeDeviceName, pObjIdIndexChangedCallback, this ); fSuccess = TRUE; } __finally { Unlock(); } } __except ( BreakOnDebuggableException() ) { TrkLog(( TRKDBG_ERROR, TEXT("Failed initializaion of volume %s (%08x)"), ptszVolumeName, GetExceptionCode() )); if( TRK_E_VOLUME_NOT_DRIVE != GetExceptionCode() ) { TrkReportInternalError( THIS_FILE_NUMBER, __LINE__, GetExceptionCode(), TRKREPORT_LAST_PARAM ); } } return fSuccess; } //+---------------------------------------------------------------------------- // // CVolume::SetLocallyGeneratedVolId // // Generate a volume ID and set it on the volume. If we're in a domain, // this will later get replaced with a volume ID from trksvr. // //+---------------------------------------------------------------------------- void CVolume::SetLocallyGeneratedVolId() { NTSTATUS status = STATUS_SUCCESS; // Ensure the volume is writeable RaiseIfWriteProtectedVolume(); _fDirty = TRUE; // Create the ID // Call _volinfo.volid.UuidCreate() RPC_STATUS rpc_status = GenerateVolumeIdInVolInfo(); if( RPC_S_OK != rpc_status ) { TrkLog(( TRKDBG_ERROR, TEXT("Couldn't create a local volid for new volume") )); TrkRaiseWin32Error(rpc_status); } // Set the ID on the volume. status = SetVolIdOnVolume( _volinfo.volid ); g_ptrkwks->_entropy.Put(); if( NT_SUCCESS(status) ) TrkLog(( TRKDBG_VOLUME, TEXT("Locally generated a new volid for %c:, %s"), VolChar(_iVol), (const TCHAR*)CDebugString(_volinfo.volid) )); else { TrkLog(( TRKDBG_ERROR, TEXT("Couldn't set new volid on %c: (%08x)"), VolChar(_iVol), status )); TrkRaiseNtStatus(status); } // Get rid of the log and existing object IDs since we have a new volid. DeleteAndReinitializeLog(); MarkForMakeAllOidsReborn(); } //+---------------------------------------------------------------------------- // // CVolume::VolumeSanityCheck // // This routine is called when the volume handles are opened, // The caller is responsible for calling CLogFile::Initialize and // CLog::Initialize. The caller must ensure that _volinfo is // properly loaded prior to the call. // //+---------------------------------------------------------------------------- const GUID s_guidInvalidVolId = { /* {d2a2ac27-b89a-11d2-9335-00805ffe11b8} */ 0xd2a2ac27, 0xb89a, 0x11d2, {0x93, 0x35, 0x00, 0x80, 0x5f, 0xfe, 0x11, 0xb8} }; void CVolume::VolumeSanityCheck( BOOL fVolIndexSetAlready ) { NTSTATUS status; CVolumeId volidVolume; const CVolumeId volNULL; const CMachineId mcidLocal( MCID_LOCAL ); TCHAR tszVolumeName[ CCH_MAX_VOLUME_NAME + 1 ]; // Get the volume name that the mount manager has associated with this // volume. LONG iVolOld = _iVol; if( !fVolIndexSetAlready ) { _iVol = MapVolumeDeviceNameToIndex( _tszVolumeDeviceName ); if( -1 == _iVol ) { TrkLog(( TRKDBG_VOLUME, TEXT("Volume %s does not appear any longer to have a drive letter (was %c:)"), _tszVolumeDeviceName, VolChar( iVolOld ) )); MarkSelfForDelete(); TrkRaiseException( TRK_E_VOLUME_NOT_DRIVE ); } else if( iVolOld != _iVol ) { TrkLog(( TRKDBG_VOLUME, TEXT("Volume %c: is now %c:"), VolChar(iVolOld), VolChar(_iVol) )); } } // Get the filesystem-maintained volume ID TCHAR tszRoot[ MAX_PATH ]; _tcscpy( tszRoot, _tszVolumeDeviceName ); _tcscat( tszRoot, TEXT("\\") ); status = QueryVolumeId(tszRoot, &volidVolume); g_ptrkwks->_entropy.Put(); if( !NT_SUCCESS(status) && STATUS_OBJECT_NAME_NOT_FOUND != status ) { // For some reason we couldn't read the NTFS volid // (e.g. it's been dismounted). TrkLog(( TRKDBG_VOLUME, TEXT("Couldn't get filesys volid for %c:"), VolChar(_iVol) )); TrkRaiseNtStatus(status); } TrkLog(( TRKDBG_VOLUME, TEXT("VolId (from NTFS) for %c: is %s"), VolChar(_iVol), (const TCHAR*)CDebugString(volidVolume) )); // Compare the volume IDs from the filesystem (volume) and from // the VolInfo structure we keep in the log file. If one is // set but not the other, then we'll adopt the one that's set. // If they're both set, but to different values, then we'll // take the one from NTFS. if( volNULL == volidVolume && volNULL != _volinfo.volid ) { // Assume the volid in the VolInfo structure is correct. // This scenario occurs after a volume is formatted while the service // is running. In that case, we have the volume info in memory and think // it's not dirty, but in fact the log file is gone. So, just to be safe, // we'll go dirty, and the flush at the end will put the latest state out // to the file. RaiseIfWriteProtectedVolume(); _fDirty = TRUE; TrkLog(( TRKDBG_ERROR, TEXT("Duping the volid from the logfile to the volume for %c:"), VolChar(_iVol) )); status = SetVolIdOnVolume(_volinfo.volid); g_ptrkwks->_entropy.Put(); if(!NT_SUCCESS(status)) { TrkLog(( TRKDBG_ERROR, TEXT("Couldn't set a volume ID on %c:"), VolChar(_iVol) )); TrkRaiseNtStatus(status); } SetState( VOL_STATE_NOTOWNED ); } else if( volidVolume != _volinfo.volid || volNULL == volidVolume) { // Either the two volids don't match, or they're both NULL. if( volNULL != volidVolume && s_guidInvalidVolId != volidVolume ) { // Assume the volid on the volume (NTFS) is correct. // If the log has a different volid, it may have invalid move entries. // So we delete it. if( volNULL != _volinfo.volid ) DeleteAndReinitializeLog(); _volinfo.Initialize(); // Set _volinfo.volid = volidVolume SetVolIdInVolInfo( volidVolume ); _volinfo.machine = mcidLocal; SetState( VOL_STATE_NOTOWNED ); } else { // Both the volume and the _volinfo (the log) are null. We're going to // go into the not-created state, but first put on a volid so that we // never have a volume with no ID. _volinfo.Initialize(); _volinfo.machine = mcidLocal; // Create a new ID and put it on the volume. SetLocallyGeneratedVolId(); // Updates _volinfo.volid // Put ourselves in the not-created state. TrkAssert( VOL_STATE_OWNED == GetState() ); SetState( VOL_STATE_NOTCREATED ); } } // If the machine ID in the log isn't the current machine, then go into // the not-created state so that we'll re-claim the volume. if( mcidLocal != _volinfo.machine ) SetState( VOL_STATE_NOTOWNED ); // See if this volume duplicates any other on this system. CVolume *pvolDuplicate = _pVolMgr->IsDuplicateVolId( this, GetVolumeId() ); if( NULL != pvolDuplicate ) { // This should never happen; there should never be two // volumes on the same machine with the same ID. // When this happens on different machines it gets caught during // CheckSequenceNumbers, but on the same machine this doesn't work, // because TrkSvr accepts the Claim of both machines. TrkLog(( TRKDBG_WARNING, TEXT("Volume %c: and %c: have duplicate volume IDs. Resetting %c:"), VolChar(GetIndex()), VolChar(pvolDuplicate->GetIndex()), VolChar(GetIndex()) )); TrkReportEvent( EVENT_TRK_SERVICE_DUPLICATE_VOLIDS, EVENTLOG_ERROR_TYPE, static_cast(CStringize( VolChar(GetIndex()))), static_cast(CStringize( VolChar(pvolDuplicate->GetIndex()) )), TRKREPORT_LAST_PARAM ); SetLocallyGeneratedVolId(); // Updates _volinfo.volid SetState( CVolume::VOL_STATE_NOTCREATED ); pvolDuplicate->SetState( CVolume::VOL_STATE_NOTOWNED ); pvolDuplicate->Release(); } // If anything's dirty, flush it now. In the normal initialization path, // this will have no effect. Flush(); } //+---------------------------------------------------------------------------- // // CVolume::Refresh // //+---------------------------------------------------------------------------- void CVolume::Refresh() { HANDLE hVolume = NULL; NTSTATUS status = STATUS_SUCCESS; Lock(); __try { status = OpenVolume( _tszVolumeDeviceName, &hVolume ); if( NT_SUCCESS(status) ) _iVol = MapVolumeDeviceNameToIndex( _tszVolumeDeviceName ); else if( !IsErrorDueToLockedVolume(status) ) _iVol = -1; if( -1 == _iVol ) { TrkLog(( TRKDBG_VOLUME, TEXT("Drive not found in CVolume::Refresh") )); MarkSelfForDelete(); } } __except( BreakOnDebuggableException() ) { } if( NULL != hVolume ) NtClose( hVolume ); Unlock(); } // CVolume::Refresh //+---------------------------------------------------------------------------- // // CVolume::MarkSelfForDelete // // Mark this CVolume to be deleted (not the volume, but the class). The // delete will actually occur when this object is completely released // and unlocked. We do, however, as part of this method remove ourself // from the volume manager's list. // //+---------------------------------------------------------------------------- void CVolume::MarkSelfForDelete() { AssertLocked(); if( !_fDeleteSelfPending ) { TrkLog(( TRKDBG_VOLUME, TEXT("Marking %c: for delete"), VolChar(_iVol) )); // Show that we need to be deleted. We can't actually delete now, because there // may be threads active in the object. _fDeleteSelfPending = TRUE; // On the final UnLock, Release will be called to counter this AddRef and // cause the actual delete. AddRef(); // Take this object out of the Volume Manager's linked list (which will do // a Release, thus the need for the above AddRef); _pVolMgr->RemoveVolumeFromLinkedList( this ); } else TrkLog(( TRKDBG_VOLUME, TEXT("%c: is already marked for delete"), VolChar(_iVol) )); } //+---------------------------------------------------------------------------- // // CVolume::RegisterPnpVolumeNotification // // Register to receive PNP notifications for this volume. If already // registered, register again (since the volume handle against which // we'd previously registered may no longer exist). // //+---------------------------------------------------------------------------- void CVolume::RegisterPnPVolumeNotification() { DEV_BROADCAST_HANDLE dbchFilter; HDEVNOTIFY hdnVolumeLock = _hdnVolumeLock; dbchFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE); dbchFilter.dbch_devicetype = DBT_DEVTYP_HANDLE; dbchFilter.dbch_handle = _ObjIdIndexChangeNotifier._hDir; // _hVolume; __try { // Register against the oid index handle (as a representative of // the volume). hdnVolumeLock = RegisterDeviceNotification((HANDLE)_ssh, &dbchFilter, DEVICE_NOTIFY_SERVICE_HANDLE); if(hdnVolumeLock == NULL) { TrkLog((TRKDBG_VOLUME, TEXT("Can't register for volume notifications, %08x"), GetLastError())); TrkRaiseLastError(); } // Get rid of our old registration, if we had one. UnregisterPnPVolumeNotification(); // Keep the new registration. _hdnVolumeLock = hdnVolumeLock; TrkLog(( TRKDBG_VOLUME, TEXT("Registered for volume lock/unlock notification on %c: (%p)"), VolChar(_iVol), _hdnVolumeLock )); } __except(BreakOnDebuggableException()) { TrkLog((TRKDBG_VOLUME, TEXT("Can't register for volume notification, %08x"), GetExceptionCode())); } } //+---------------------------------------------------------------------------- // // CVolume::UnregisterPnpVolumeNotification // // Unregister the device notification handle for this volume (if we have // one). // //+---------------------------------------------------------------------------- void CVolume::UnregisterPnPVolumeNotification() { if(_hdnVolumeLock) { if( !UnregisterDeviceNotification(_hdnVolumeLock)) { TrkLog(( TRKDBG_ERROR, TEXT("UnregisterDeviceNotification failed: %lu"), GetLastError() )); } TrkLog(( TRKDBG_VOLUME, TEXT("Unregistered for volume lock/unlock notification on %c: (%p)"), VolChar(_iVol), _hdnVolumeLock )); _hdnVolumeLock = NULL; } } //+---------------------------------------------------------------------------- // // CVolume::DeleteAndReinitializeLog // // Delete the volume log and reinitialize it. // //+---------------------------------------------------------------------------- void CVolume::DeleteAndReinitializeLog() { // Delete and reinitialize the log __try { RaiseIfWriteProtectedVolume(); TrkLog(( TRKDBG_VOLUME, TEXT("DeleteAndReinitializeLog (%s)"), _tszVolumeDeviceName )); if( IsHandsOffVolumeMode() ) // Volume is locked TrkRaiseNtStatus( STATUS_ACCESS_DENIED ); // Delete the log _cLogFile.Delete(); // Reinitialize the log file, then the log itself. _cLogFile.Initialize( NULL, NULL, NULL, VolChar(_iVol) ); _cLog.Initialize( _pLogCallback, _pTrkWksConfiguration, &_cLogFile ); } __except( IsErrorDueToLockedVolume( GetExceptionCode() ) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { // If the volume is locked, start the reopen timer and abort. CloseVolumeHandles(); // Never raises g_ptrkwks->SetReopenVolumeHandlesTimer(); TrkRaiseException( GetExceptionCode() ); } } //+---------------------------------------------------------------------------- // // CVolume::DeleteLogAndReInitializeVolume // // Delete and reinit the log, then reinitialize the rest of the volume. // //+---------------------------------------------------------------------------- void CVolume::DeleteLogAndReInitializeVolume() { AssertLocked(); // There's the remote possibility that the VolumeSanityCheck // call below will call this routine. Just to be paranoid, we // add protection against an infinite recursion. if( _fDeleteLogAndReInitializeVolume ) TrkRaiseWin32Error( ERROR_OPEN_FAILED ); _fDeleteLogAndReInitializeVolume = TRUE; __try { TrkLog(( TRKDBG_VOLUME, TEXT("Re-initializing volume %c:"), VolChar(_iVol) )); // Re-initialize the CLogFile. DeleteAndReinitializeLog(); // Recreate the volinfo in the new log's header _fDirty = TRUE; // Force a flush VolumeSanityCheck(); } __finally { _fDeleteLogAndReInitializeVolume = FALSE; } } //+---------------------------------------------------------------------------- // // CVolume::UnInitialize // // Unregister our PNP handle, free critical sections, etc. // //+---------------------------------------------------------------------------- void CVolume::UnInitialize() { if( _fInitialized ) { IFDBG( _cLocks++; ) UnregisterPnPVolumeNotification(); _ssh = NULL; if (_hVolume != NULL) NtClose(_hVolume); __try { _cLogFile.UnInitialize(); } __except( EXCEPTION_EXECUTE_HANDLER ) // BreakOnDebuggableException() ) { TrkLog(( TRKDBG_ERROR, TEXT("Ignoring exception in CVolume::UnInitialize after _cLogFile.UnInitialize for %c: %08x"), VolChar(_iVol), GetExceptionCode() )); } _fInitialized = FALSE; _csHandles.UnInitialize(); _csVolume.UnInitialize(); IFDBG( _cLocks--; ) TrkAssert( 0 == _cLocks ); } _ObjIdIndexChangeNotifier.UnInitialize(); } //+---------------------------------------------------------------------------- // // CVolume::Flush // // Flush the volinfo structure, the log, and the logfile. In the process, // mark the logfile header to show a proper shutdown. If we're in the middle // of a service shutdown, and there's a problem with the log, don't run // the recovery code. // //+---------------------------------------------------------------------------- void CVolume::Flush(BOOL fServiceShutdown) { Lock(); __try { if( _fDirty ) SaveVolInfo(); __try { _cLog.Flush( ); // Flushes to CLogFile _cLogFile.SetShutdown( TRUE ); // Causes a flush to disk if necessary } __except( !fServiceShutdown && 0 == _cHandleLocks && IsRecoverableDiskError( GetExceptionCode() ) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { // Note that we don't handle this exception if the _cHandleLocks is non-zero. // In this case we're in fast-path and must complete quickly, and the following // calls could be too time consuming. We must complete quickly because // CloseVolumeHandles uses that lock, and that call might be called on the // SCM thread (for e.g. a volume lock event). The SCM thread is shared by // all services in the process, so we must fast-path anything on it. if( IsErrorDueToLockedVolume( GetExceptionCode() )) { CloseAndReopenVolumeHandles(); // Reopen might fail _cLog.Flush(); _cLogFile.SetShutdown( TRUE ); } else { TrkAssert( TRK_E_CORRUPT_LOG == GetExceptionCode() ); TrkReportEvent( EVENT_TRK_SERVICE_CORRUPT_LOG, EVENTLOG_ERROR_TYPE, static_cast( CStringize( VolChar(_iVol) )), NULL ); DeleteLogAndReInitializeVolume(); } } } __finally { Unlock(); } } //+---------------------------------------------------------------------------- // // CVolume::OpenFile // // Open a file on this volume, given the file's object ID. // //+---------------------------------------------------------------------------- BOOL CVolume::OpenFile( const CObjId &objid, ACCESS_MASK AccessMask, ULONG ShareAccess, HANDLE *ph) { NTSTATUS status; Lock(); __try { status = OpenFileById( _tszVolumeDeviceName, objid, AccessMask, ShareAccess, 0, ph ); if( NT_SUCCESS(status) ) return TRUE; else if( STATUS_OBJECT_NAME_NOT_FOUND == status ) return FALSE; else TrkRaiseNtStatus( status ); } __finally { Unlock(); } return( FALSE ); } // CVolume::OpenFile() //+---------------------------------------------------------------------------- // // CVolume::LoadSyncVolume // // Load the TRKSVR_SYNC_VOLUME message request for this volume, if necessary. // The call of this message to trksvr is actually sent by the caller. On // return of that request, UnloadSyncVolume method will be called. // //+---------------------------------------------------------------------------- BOOL CVolume::LoadSyncVolume( TRKSVR_SYNC_VOLUME *pSyncVolume, EAggressiveness eAggressiveness, BOOL* pfSyncNeeded ) { CVOL_STATE state = GetState(); BOOL fSuccess = FALSE; Lock(); __try { if( !_fVolInfoInitialized ) TrkRaiseException( E_UNEXPECTED ); memset( pSyncVolume, 0, sizeof(*pSyncVolume) ); if(pfSyncNeeded) { *pfSyncNeeded = FALSE; } // See if it's time to transition from not-owned to not-created. if( NotOwnedExpired() ) SetState( state = VOL_STATE_NOTCREATED ); // Load the message request, if necessary, based on our current state. if(state == VOL_STATE_NOTCREATED) { // Ordinarily, if we were unable to create this volume due to volume // quota, we won't try again. But if we're told to be aggressive, // we'll try anyway. if( PASSIVE == eAggressiveness && _VolQuotaReached.IsSet() ) { TrkLog(( TRKDBG_VOLUME, TEXT("Not attempting to create new volume ID on %c:; quota reached"), VolChar(_iVol) )); } else { // Generate a new secret for authentication of this volume. g_ptrkwks->_entropy.Put(); if( !g_ptrkwks->_entropy.InitializeSecret( & _tempSecret ) ) { // This should never happen - even if there hasn't been enough // entropy yet, more will be generated. TrkLog(( TRKDBG_ERROR, TEXT("Couldn't generate secret for volume %c:"), VolChar(_iVol) )); goto Exit; } TrkLog((TRKDBG_VOLUME, TEXT("Generated secret %s for volume %c"), (const TCHAR*)CDebugString(_tempSecret), VolChar( _iVol ))); // Put the secret in the request, and set the request type to "create" pSyncVolume->secret = _tempSecret; pSyncVolume->SyncType = CREATE_VOLUME; // Show that we put data into the request that should be sent // to trksvr. if (pfSyncNeeded != NULL) *pfSyncNeeded = TRUE; } } // case CREATE_VOLUME else if(state == VOL_STATE_NOTOWNED) { // Attempt to claim this volume. pSyncVolume->volume = _volinfo.volid; pSyncVolume->secretOld = _volinfo.secret; pSyncVolume->SyncType = CLAIM_VOLUME; // Generate a new secret. if( !g_ptrkwks->_entropy.InitializeSecret( &_tempSecret ) ) { TrkLog(( TRKDBG_ERROR, TEXT("Couldn't generate secret for volume %c:"), VolChar(_iVol) )); goto Exit; } TrkLog((TRKDBG_VOLUME, TEXT("Generated secret %s for volume %c"), (const TCHAR*)CDebugString(_tempSecret), VolChar( _iVol ))); pSyncVolume->secret = _tempSecret; // Show that the request should be sent. if (pfSyncNeeded != NULL) *pfSyncNeeded = TRUE; } // case CLAIM_VOLUME } __except( BreakOnDebuggableException() ) { TrkLog(( TRKDBG_ERROR, TEXT("Ignoring exception in LoadSyncVolume for %c: %08x"), VolChar(_iVol), GetExceptionCode() )); goto Exit; } fSuccess = TRUE; Exit: Unlock(); return( fSuccess ); } //+---------------------------------------------------------------------------- // // CVolume::OnRestore // // Not currently implemented. // //+---------------------------------------------------------------------------- HRESULT CVolume::OnRestore() { return( E_NOTIMPL ); #if 0 HRESULT hr = E_FAIL; CVolumeId volidVolume; const CMachineId mcidLocal( MCID_LOCAL ); NTSTATUS status; memset( &_volinfo, 0, sizeof(_volinfo) ); hr = S_OK; Lock(); __try // __finally { __try { // Get volume id from two different places: in the log file, and on // the volume. If the two // disagree, use the object id in the log file, overwrite the other // one. Put the volume into NOTOWNED state. TrkLog(( TRKDBG_VOLUME, TEXT("Checking for recorded id's on volume %c:"), VolChar(_iVol) )); LoadVolInfo(); TrkLog(( TRKDBG_VOLUME, TEXT("volume id in log file ---- (%s) %c:"), CDebugString(_volinfo.volid)._tsz, VolChar(_iVol) )); status = QueryVolumeId(_tszVolumeDeviceName, &volidVolume); if(!NT_SUCCESS(status) && status != STATUS_OBJECT_NAME_NOT_FOUND) { TrkLog((TRKDBG_ERROR, TEXT("Can't get id from volume %c"), VolChar(_iVol))); SetState(VOL_STATE_NOTCREATED); } // if no id is set on the volume, adopt from the log file if(volidVolume == CVolumeId() && _volinfo.volid != CVolumeId()) { status = SetVolIdOnVolume(_volinfo.volid); g_ptrkwks->_entropy.Put(); if(!NT_SUCCESS(status)) { TrkRaiseNtStatus(status); } SetState( VOL_STATE_NOTOWNED ); } else if(volidVolume != _volinfo.volid) // The log file could have been copied or moved before the restore // happened, in order to be safe we trash the volume. { SetState(VOL_STATE_NOTCREATED); } hr = S_OK; } __except (BreakOnDebuggableException()) { TrkLog((TRKDBG_ERROR, TEXT("OnRestore failed"))); hr = GetExceptionCode(); } // If an un-recoverable log error was raised, re-initialize everything if( TRK_E_CORRUPT_LOG == hr ) { TrkReportEvent( EVENT_TRK_SERVICE_CORRUPT_LOG, EVENTLOG_ERROR_TYPE, static_cast( CStringize( VolChar(_iVol) )), NULL ); __try { DeleteLogAndReInitializeVolume(); } __except( BreakOnDebuggableException()) { hr = GetExceptionCode(); } } else if( IsErrorDueToLockedVolume(hr) ) { CloseAndReopenVolumeHandles(); // Reopen might fail TrkRaiseException( hr ); } } __finally { Unlock(); } return hr; #endif // #if 0 } //+---------------------------------------------------------------------------- // // CVolume::LoadQueryVolume // // Load a TRKSVR_SYNC_VOLUME request for this volume, if necessary. If we // load it, the caller will send it to trksvr. On return of that request, // UnloadQueryVolume method will be called. // //+---------------------------------------------------------------------------- BOOL CVolume::LoadQueryVolume( TRKSVR_SYNC_VOLUME *pQueryVolume ) { BOOL fSuccess = FALSE; Lock(); __try { // Don't do anything we're not even in trksvr. if(GetState() == VOL_STATE_NOTCREATED) { goto Exit; } // Put our volid & log sequence number into the request. memset( pQueryVolume, 0, sizeof(*pQueryVolume) ); pQueryVolume->SyncType = QUERY_VOLUME; pQueryVolume->volume = _volinfo.volid; pQueryVolume->seq = _cLog.GetNextSeqNumber(); // Never raises } __except( BreakOnDebuggableException() ) { TrkLog(( TRKDBG_ERROR, TEXT("Ignoring exception in LoadQueryVolume for %c: %08x"), VolChar(_iVol), GetExceptionCode() )); goto Exit; } fSuccess = TRUE; Exit: Unlock(); return( fSuccess ); } // Originally, trkwks bundle up volume requests (sync, claim, create) and call the DC. After the // DC returns, this function is called to put necessary information back on the volume. Now the // DC callback mechanism is added. When there are create volume requests, the DC will callback and // this function is called by the DC callback function. The DC needs to know if each create volume // request is successfully finished by the trkwks, so this function has to put an HRESULT to // indicate that in the hr field of the TRKSVR_SYNC_VOLUME structure. //+---------------------------------------------------------------------------- // // CVolume::UnloadSyncVolume // // A sync-volume request was loaded in LoadSyncVolume, sent to trksvr, and // we now need to interpret the result. If we successfully completed // a create or claim, we'll go into the owned state. // //+---------------------------------------------------------------------------- BOOL CVolume::UnloadSyncVolume( TRKSVR_SYNC_VOLUME *pSyncVolume ) { BOOL fSuccess = FALSE; BOOL fWrite = FALSE; CMachineId mcidLocal( MCID_LOCAL ); Lock(); __try { if( !_fVolInfoInitialized ) TrkRaiseException( E_UNEXPECTED ); if(pSyncVolume->hr == S_OK) { // Clear the bit that indicates we've reported a vol quota event. // That way, the next time we get a volume quota error, we'll report // to the event log. _VolQuotaReached.Clear(); switch( pSyncVolume->SyncType ) { case CREATE_VOLUME: { NTSTATUS status = STATUS_SUCCESS; // Write the volume ID to the volume meta-data. status = SetVolIdOnVolume( pSyncVolume->volume ); g_ptrkwks->_entropy.Put(); if( !NT_SUCCESS(status) ) __leave; TrkLog(( TRKDBG_VOLUME, TEXT("Newly-created vol id = %s, %c:"), (const TCHAR*)CDebugString(pSyncVolume->volume), VolChar(_iVol) )); // Create a fresh log DeleteAndReinitializeLog(); // Update _volinfo _fDirty = TRUE; _volinfo.cftLastRefresh = pSyncVolume->ftLastRefresh; // Set _volinfo.volid = pSyncVolume->volume SetVolIdInVolInfo( pSyncVolume->volume ); _volinfo.machine = mcidLocal; _volinfo.secret = _tempSecret; // And update our state. SetState( VOL_STATE_OWNED ); // Flushes _volinfo TrkAssert( VOL_STATE_OWNED == GetState() ); TrkReportEvent( EVENT_TRK_SERVICE_VOLUME_CREATE, EVENTLOG_INFORMATION_TYPE, static_cast( CStringize( VolChar(_iVol) )), static_cast( CStringize( _volinfo.volid )), NULL ); } // case CREATE_VOLUME break; case CLAIM_VOLUME: { RaiseIfWriteProtectedVolume(); _fDirty = TRUE; _volinfo.machine = mcidLocal; _volinfo.cftLastRefresh = pSyncVolume->ftLastRefresh; _volinfo.secret = _tempSecret; SetState( VOL_STATE_OWNED ); // Flushes _volinfo TrkAssert( VOL_STATE_OWNED == GetState() ); Seek( pSyncVolume->seq ); TrkReportEvent( EVENT_TRK_SERVICE_VOLUME_CLAIM, EVENTLOG_INFORMATION_TYPE, static_cast( CStringize( VolChar(_iVol) )), static_cast( CStringize( _volinfo.volid )), TRKREPORT_LAST_PARAM ); } // case CLAIM_VOLUME break; default: TrkAssert( FALSE && TEXT("Invalid SyncType given to CVolume::Serialize") ); break; } // switch fSuccess = TRUE; } // if(pSyncVolume->hr == S_OK) else { // If this is a quota error, log it (but only log it once // per machine per transition). if( TRK_E_VOLUME_QUOTA_EXCEEDED == pSyncVolume->hr ) { TrkLog(( TRKDBG_ERROR, TEXT("Vol quota reached") )); if( !_VolQuotaReached.IsSet() ) { _VolQuotaReached.Set(); TrkReportEvent( EVENT_TRK_SERVICE_VOL_QUOTA_EXCEEDED, EVENTLOG_WARNING_TYPE, TRKREPORT_LAST_PARAM ); } // We'll call this success so that we don't retry. We'll try again // later when the infrequent timer goes off. fSuccess = TRUE; } SetState(VOL_STATE_NOTOWNED); __leave; } } __except( BreakOnDebuggableException() ) { TrkLog(( TRKDBG_ERROR, TEXT("Ignoring exception in UnloadSyncVolume for %c: %08x"), VolChar(_iVol), GetExceptionCode() )); } if( !fSuccess && pSyncVolume->SyncType == CREATE_VOLUME ) { g_ptrkwks->_entropy.ReturnUnusedSecret( & _tempSecret ); _tempSecret = CVolumeSecret(); if( SUCCEEDED(pSyncVolume->hr) ) pSyncVolume->hr = E_FAIL; } Unlock(); return( fSuccess ); } // CVolume::UnloadSyncVolume //+---------------------------------------------------------------------------- // // CVolume::UnloadQueryVolume // // The volume manager called LoadQueryVolume, sent the request to trskvr, // and is now giving us the result. // //+---------------------------------------------------------------------------- BOOL CVolume::UnloadQueryVolume( const TRKSVR_SYNC_VOLUME *pQueryVolume ) { BOOL fSuccess = FALSE; Lock(); __try { // Was the request successful? if(pQueryVolume->hr == S_OK) { // Go into the owned state, if we're not there // already. SetState( VOL_STATE_OWNED ); // Seek the log to match what trksvr expects. If this causes the // seek pointer to be backed up, it will set the timer to trigger // a new move-notification to trksvr. Seek( pQueryVolume->seq ); } else // DC didn't return VOLUME_OK { TrkLog((TRKDBG_VOLUME, TEXT("DC returned %s for QueryVolume of volume %s (%c:) -> VOL_STATE_NOTOWNED"), GetErrorString(pQueryVolume->hr), (const TCHAR*)CDebugString(pQueryVolume->volume), VolChar(_iVol) )); // If there was a problem, go into the not-owned state. SetState(VOL_STATE_NOTOWNED); } } __except( BreakOnDebuggableException() ) { TrkLog(( TRKDBG_ERROR, TEXT("Ignoring exception in UnloadQueryVolume for %c: %08x"), VolChar(_iVol), GetExceptionCode() )); goto Exit; } fSuccess = TRUE; Exit: Unlock(); return( fSuccess ); } //+---------------------------------------------------------------------------- // // CVolume::Append // // Append a move notification to the end of this volume's log. // //+---------------------------------------------------------------------------- void CVolume::Append( const CDomainRelativeObjId &droidCurrent, const CDomainRelativeObjId &droidNew, const CMachineId &mcidNew, const CDomainRelativeObjId &droidBirth) { //TrkLog((TRKDBG_VOL_REFCNT, TEXT("CVolume(%08x)::Append refcnt=%d (should be 2, sometimes >2)"), this, _lRef)); Lock(); __try // __finally { // Validate the IDs const CVolumeId volidZero; const CObjId objidZero; if( volidZero == droidCurrent.GetVolumeId() || objidZero == droidCurrent.GetObjId() || volidZero == droidNew.GetVolumeId() || objidZero == droidNew.GetObjId() || volidZero == droidBirth.GetVolumeId() || objidZero == droidBirth.GetObjId() ) { // In the append path, we only raise NTSTATUS errors, not HRESULTs TrkRaiseException( STATUS_OBJECT_NAME_INVALID ); } __try // __except { _cLog.Append( droidCurrent.GetVolumeId(), droidCurrent.GetObjId(), droidNew, mcidNew, droidBirth ); } __except( IsRecoverableDiskError( GetExceptionCode() ) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { // We had a potentially recoverable exception. Try to handle it // and retry the append. if( IsErrorDueToLockedVolume( GetExceptionCode() ) ) { CloseAndReopenVolumeHandles(); // Reopen might fail } else { TrkAssert( TRK_E_CORRUPT_LOG == GetExceptionCode() ); TrkReportEvent( EVENT_TRK_SERVICE_CORRUPT_LOG, EVENTLOG_ERROR_TYPE, static_cast( CStringize( VolChar(_iVol) )), NULL ); // The log is corrupted. Re-initialize, then attempt the Append again. // If this raises, it's an unrecoverable exception, so we just pass // it up. DeleteLogAndReInitializeVolume(); } // Retry the Append, which could raise again, but this time we won't catch it. _cLog.Append( droidCurrent.GetVolumeId(), droidCurrent.GetObjId(), droidNew, mcidNew, droidBirth ); } } __finally { Unlock(); } } //+---------------------------------------------------------------------------- // // CVolume::Read // // Read one or more entries from the log, from the current seek position. // //+---------------------------------------------------------------------------- void CVolume::Read(CObjId *pobjidCurrent, CDomainRelativeObjId *pdroidBirth, CDomainRelativeObjId *pdroidNew, SequenceNumber *pseqFirst, ULONG *pcRead) { Lock(); __try // __finally { __try { _cLog.Read( pobjidCurrent, pdroidBirth, pdroidNew, pseqFirst, pcRead ); } __except( IsRecoverableDiskError( GetExceptionCode() ) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { // Try to recover from this error and if possible retry // the read. if( IsErrorDueToLockedVolume( GetExceptionCode() )) { CloseAndReopenVolumeHandles(); // Reopen might fail // Retry the read, which could raise again, but this time we won't // catch it. TrkLog(( TRKDBG_VOLUME, TEXT("Retrying CLog::Read") )); _cLog.Read( pobjidCurrent, pdroidBirth, pdroidNew, pseqFirst, pcRead ); } else { TrkAssert( TRK_E_CORRUPT_LOG == GetExceptionCode() ); TrkReportEvent( EVENT_TRK_SERVICE_CORRUPT_LOG, EVENTLOG_ERROR_TYPE, static_cast( CStringize( VolChar(_iVol) )), NULL ); // The log is corrupted. Re-initialize, then pass up the error. DeleteLogAndReInitializeVolume(); TrkRaiseException( GetExceptionCode() ); } } } __finally { Unlock(); } } //+---------------------------------------------------------------------------- // // CVolume::Search // // Search the log for a move-notification (from droidCurrent). // //+---------------------------------------------------------------------------- BOOL CVolume::Search( const CDomainRelativeObjId & droidCurrent, CDomainRelativeObjId * pdroidNew, CMachineId *pmcidNew, CDomainRelativeObjId * pdroidBirth ) { BOOL fFound = FALSE; Lock(); __try // __finally { __try { // Perfbug: Don't hold the log locked during the whole search such that // it locks out Appends. fFound = _cLog.Search( droidCurrent.GetObjId(), pdroidNew, pmcidNew, pdroidBirth ); } __except( IsRecoverableDiskError( GetExceptionCode() ) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { // Try to recover from this error and retry the search. if( IsErrorDueToLockedVolume( GetExceptionCode() )) { CloseAndReopenVolumeHandles(); // Reopen might fail // Retry the search, which could raise again, but this time we won't catch it. fFound = _cLog.Search( droidCurrent.GetObjId(), pdroidNew, pmcidNew, pdroidBirth ); } else { TrkAssert( TRK_E_CORRUPT_LOG == GetExceptionCode() ); TrkReportEvent( EVENT_TRK_SERVICE_CORRUPT_LOG, EVENTLOG_ERROR_TYPE, static_cast( CStringize( VolChar(_iVol) )), NULL ); // The log is corrupted. Re-initialize, and pass up the error. DeleteLogAndReInitializeVolume(); TrkRaiseException( GetExceptionCode() ); } } } __finally { Unlock(); } return( fFound ); } //+---------------------------------------------------------------------------- // // CVolume::Seek // // Seek the log to a particular sequence number. // //+---------------------------------------------------------------------------- BOOL CVolume::Seek( SequenceNumber seq ) { BOOL fSuccess = FALSE; Lock(); __try { __try { fSuccess = _cLog.Seek( seq ); } __except( IsRecoverableDiskError( GetExceptionCode() ) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { // Try to recover from this error and retry the seek. if( IsErrorDueToLockedVolume( GetExceptionCode() )) { CloseAndReopenVolumeHandles(); // Reopen might fail // Retry the Seek, which could raise again, but this time we won't catch it. fSuccess = _cLog.Seek( seq ); } else { TrkAssert( TRK_E_CORRUPT_LOG == GetExceptionCode() ); TrkReportEvent( EVENT_TRK_SERVICE_CORRUPT_LOG, EVENTLOG_ERROR_TYPE, static_cast( CStringize( VolChar(_iVol) )), NULL ); // The log is corrupted. Re-initialize and pass up the error. DeleteLogAndReInitializeVolume(); TrkRaiseException( GetExceptionCode() ); } } } __finally { Unlock(); } if( fSuccess ) TrkLog(( TRKDBG_VOLUME, TEXT("Log on %c: sought to seq %d"), VolChar(_iVol), seq )); else TrkLog(( TRKDBG_VOLUME, TEXT("Log on %c: couldn't be sought to seq %d"), VolChar(_iVol), seq )); return( fSuccess ); } //+---------------------------------------------------------------------------- // // CVolume::Seek // // Seek to a relative (e.g. back up 2) or absolute (e.g. first) position. // //+---------------------------------------------------------------------------- void CVolume::Seek( int origin, int iSeek ) { Lock(); __try // __finally { __try { _cLog.Seek( origin, iSeek ); } __except( IsRecoverableDiskError( GetExceptionCode() ) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { // Attempt to recover from this error and retry the seek. if( IsErrorDueToLockedVolume( GetExceptionCode() )) { CloseAndReopenVolumeHandles(); // Reopen might fail // Retry the Seek, which could raise again, but this time we won't catch it. _cLog.Seek( origin, iSeek ); } else { TrkAssert( TRK_E_CORRUPT_LOG == GetExceptionCode() ); TrkReportEvent( EVENT_TRK_SERVICE_CORRUPT_LOG, EVENTLOG_ERROR_TYPE, static_cast( CStringize( VolChar(_iVol) )), NULL ); // The log is corrupted. Re-initialize and pass up the error. DeleteLogAndReInitializeVolume(); TrkRaiseException( GetExceptionCode() ); } } } __finally { Unlock(); } } //+---------------------------------------------------------------------------- // // CVolume::GetVolumeId // // Get the volume ID without taking the lock. This was done // so that CVolumeManager::IsDuplicateID can check the volid // of other volumes without deadlocking. Otherwise we run the // risk of one volume holding its locks and trying to get // another volume's lock (using GetVolumeId on that volume) // while another thread is in that volume doing the same for // this volume. // //+---------------------------------------------------------------------------- const CVolumeId CVolume::GetVolumeId() { CVolumeId volid; ULONG cAttempts = 0; // Spin until we get a good volid. while( TRUE ) { // Get the update counter before and after reading // from the volid. (This assumes that // reading the long is atomic.) LONG lVolidUpdatesBefore = _lVolidUpdates; volid = _volinfo.volid; LONG lVolidUpdatesAfter = _lVolidUpdates; // When the _volinfo is updated, the _lVolidUpdates is // incremented before and after the update. So if there // was an update in progress when we started, it will // be an odd number. // // Ensure there was no update in progress when we read // the volid, and there was no update started while we // were reading the volid. if( (lVolidUpdatesBefore & 1) || lVolidUpdatesBefore != lVolidUpdatesAfter ) { // Check for timeout (30 seconds) if( 3000 < ++cAttempts ) { TrkLog(( TRKDBG_ERROR, TEXT("Failed spin in GetVolumeId") )); TrkRaiseWin32Error( WAIT_TIMEOUT ); } // Wait for the update to complete then try again. Sleep( 10 ); continue; } else break; } return( volid ); } //+---------------------------------------------------------------------------- // // CVolume::GetState // // Get the current state of this volume (owned, not-owned, or not-created). // //+---------------------------------------------------------------------------- CVolume::CVOL_STATE CVolume::GetState() { CVolumeId volNULL; CVOL_STATE state = VOL_STATE_UNKNOWN; Lock(); __try { if( _volinfo.fNotCreated ) { state = VOL_STATE_NOTCREATED; } // If the time we entered the not-owned state is non-zero, then // we're certainly in the not-owned state. Also, if the machine ID // in the _volinfo header doesn't match the local machine, we're // not owned. else if(_volinfo.cftEnterNotOwned != 0 || _volinfo.machine != CMachineId(MCID_LOCAL) ) { // Is this the first time that we realized we're not owned? if( !IsWriteProtectedVolume() && 0 == _volinfo.cftEnterNotOwned ) { _volinfo.cftEnterNotOwned.SetToUTC(); _fDirty = TRUE; Flush(); } state = VOL_STATE_NOTOWNED; } // Otherwise, we must be properly owned. else { state = VOL_STATE_OWNED; } } __finally { Unlock(); } return state; } //+---------------------------------------------------------------------------- // // CVolume::SetState // // Change the current state of the volume. This checks for valid transitions. // For example, you can't transition from not-created to not-owned (such // a request is silently ignored). This alleviates the caller from having // to perform this logic. // //+---------------------------------------------------------------------------- void CVolume::SetState(CVOL_STATE volstateTarget) { CVOL_STATE volstateCurrent = GetState(); NTSTATUS status = STATUS_SUCCESS; VolumePersistentInfo volinfoNew = _volinfo; BOOL fDirtyNew = _fDirty; // Make sure the volume is writeable RaiseIfWriteProtectedVolume(); Lock(); __try { switch( volstateTarget ) { case VOL_STATE_NOTOWNED: // We can only go to not-owned from owned. if( VOL_STATE_OWNED == volstateCurrent ) { TrkAssert( !volinfoNew.fNotCreated ); TrkLog(( TRKDBG_VOLUME, TEXT("Entering not-owned state on vol %c:"), VolChar(_iVol) )); RaiseIfWriteProtectedVolume(); fDirtyNew = TRUE; volinfoNew.cftEnterNotOwned.SetToUTC(); } break; case VOL_STATE_NOTCREATED: // We can always go to not-created. if( volstateCurrent != VOL_STATE_NOTCREATED ) { TrkLog(( TRKDBG_VOLUME, TEXT("Entering not-created state on vol %c:"), VolChar(_iVol) )); RaiseIfWriteProtectedVolume(); fDirtyNew = TRUE; volinfoNew.fNotCreated = TRUE; volinfoNew.cftEnterNotOwned = CFILETIME(0); } break; case VOL_STATE_OWNED: if( VOL_STATE_NOTCREATED == volstateCurrent ) { // We're going from not-created to owned, so we need to make // all our OIDs reborn. TrkLog(( TRKDBG_VOLUME, TEXT("Entering owned state (from not-created) on vol %c:"), VolChar(_iVol) )); RaiseIfWriteProtectedVolume(); fDirtyNew = TRUE; volinfoNew.fNotCreated = FALSE; TrkAssert( CFILETIME(0) == volinfoNew.cftEnterNotOwned ); // Since we now have a new volid, we must give all the existing // files new object IDs. //MarkForMakeAllOidsReborn(); volinfoNew.fDoMakeAllOidsReborn = TRUE; } else if( VOL_STATE_NOTOWNED == volstateCurrent ) { // We're going from not-owned to owned. TrkLog(( TRKDBG_VOLUME, TEXT("Entering owned state (from not-owned) on vol %c:"), VolChar(_iVol) )); TrkAssert( !volinfoNew.fNotCreated ); RaiseIfWriteProtectedVolume(); fDirtyNew = TRUE; volinfoNew.cftEnterNotOwned = CFILETIME(0); } TrkAssert( CVolumeId() != _volinfo.volid ); break; default: TrkAssert( !TEXT("Bad target state in CVolume::SetState") ); } // switch( volstateTarget ) // If we modified the volinfo, write it back out. _volinfo = volinfoNew; _fDirty |= fDirtyNew; Flush(); } __finally { Unlock(); } return; } //+---------------------------------------------------------------------------- // // CVolume::NotOwnedExpired // // Have we been in the not-owned state for long enough that we should be // in the not-created state? // //+---------------------------------------------------------------------------- BOOL CVolume::NotOwnedExpired() { Lock(); __try { if(_volinfo.cftEnterNotOwned != 0) { CFILETIME cftDiff = CFILETIME() - _volinfo.cftEnterNotOwned; ULONG SecondsDiff = static_cast((LONGLONG)cftDiff/10000000); if(SecondsDiff > _pTrkWksConfiguration->GetVolNotOwnedExpireLimit()) { return TRUE; } } } __finally { Unlock(); } return FALSE; } //+---------------------------------------------------------------------------- // // CVolume::MakeAllOidsReborn // // Reset (zero out) the birth IDs (actually, all 48 extended bytes) of // all the files on this volume. That makes the file no longer a link source, // so we won't try to track it. If someone subsequently makes a link // to it, NTFS will fill in a new birth ID. // //+---------------------------------------------------------------------------- BOOL CVolume::MakeAllOidsReborn() { CObjIdEnumerator oie; BOOL fSuccess = FALSE; CObjId objid; CDomainRelativeObjId droidBirth; NTSTATUS status; CVolumeId vidNull; BOOL fLocked = FALSE; __try { // Give all the files with object IDs a fresh birth ID, as if the // file had first been linked to on this volume. TrkLog(( TRKDBG_VOLUME, TEXT("Making OIDs reborn on volume %c:"), VolChar(_iVol) )); if(oie.Initialize(_tszVolumeDeviceName)) { if(oie.FindFirst(&objid, &droidBirth)) { do { g_ptrkwks->RaiseIfStopped(); // If this has what looks like an invalid birth ID, ignore it. if( CObjId() == droidBirth.GetObjId() ) continue; // We only take the lock directly around the make-reborn // call, since with the sleep below we could be in this routine // for a while. Lock(); fLocked = TRUE; TrkAssert( 1 == _cLocks ); MakeObjIdReborn( _tszVolumeDeviceName, objid ); Unlock(); fLocked = FALSE; Sleep( 100 ); // don't hog the machine } while(oie.FindNext(&objid, &droidBirth)); } } } __except( BreakOnDebuggableException() ) { __try { if( TRK_E_CORRUPT_LOG == GetExceptionCode() ) { TrkReportEvent( EVENT_TRK_SERVICE_CORRUPT_LOG, EVENTLOG_ERROR_TYPE, static_cast( CStringize( VolChar(_iVol) )), NULL ); BreakIfRequested(); DeleteLogAndReInitializeVolume(); } else if( IsErrorDueToLockedVolume( GetExceptionCode() )) { CloseAndReopenVolumeHandles(); TrkRaiseException( GetExceptionCode() ); } TrkLog(( TRKDBG_ERROR, TEXT("Ignoring exception in CVolume::MakeAllOidsReborn for %c: %08x"), VolChar(_iVol), GetExceptionCode() )); } __finally { if( fLocked ) Unlock(); } goto Exit; } fSuccess = TRUE; Exit: oie.UnInitialize(); if(!fSuccess) { TrkLog((TRKDBG_ERROR, TEXT("Can't delete all object ids on volume %c:"), VolChar(_iVol) )); } return fSuccess; } //+---------------------------------------------------------------------------- // // CVolume::OnHandlesMustClose // // This routine is called from CLogFile if it discovers that the log file // needs to be closed (an oplock break). We close all handles on the volume // and start the reopen timer. // //+---------------------------------------------------------------------------- void CVolume::OnHandlesMustClose() { CloseVolumeHandles(); // Doesn't raise g_ptrkwks->SetReopenVolumeHandlesTimer(); } //+---------------------------------------------------------------------------- // // CVolume::FileActionIdNotTunnelled // // This method is called as an event notification, indicating that NTFS has // notified us that a file could not be tunnelled. We do the tunnelling manually // here. // //+---------------------------------------------------------------------------- #define ON_NOT_TUNNELLED_DELAY 500 // .5 seconds void CVolume::FileActionIdNotTunnelled( FILE_OBJECTID_INFORMATION * poi ) { ULONG ulMillisecondsSleptSoFar = 0; HANDLE hFile = NULL; // We don't take the volume lock here. So don't attempt to // do anything other than simple I/O. We don't take the lock because // we want to ensure that tunnelling is resolved quickly without // getting blocked. // // Open the file being "tunnelled from" by OBJECTID // Delete the object id // Close // Open the file being "tunnelled to" by FileReference // Set the object id and extra data // Close // Test hook IFDBG( _pTunnelTest->ReleaseAndWait() ); __try { if (_hVolume == NULL) { // Couldn't reopen the volume __leave; } NTSTATUS Status; OBJECT_ATTRIBUTES oa; UNICODE_STRING uId; IO_STATUS_BLOCK ios; CObjId objid( FOI_OBJECTID, *poi ); int i; // Ignore if this isn't a link tracking (e.g. it's an NTFRS) object ID. if( CObjId() == CObjId(FOI_BIRTHID, *poi) ) { TrkLog(( TRKDBG_VOLUME, TEXT("Ignoring not-tunneled notification for %s"), (const TCHAR*)CDebugString( objid ) )); __leave; } uId.Length = sizeof(poi->ObjectId); uId.MaximumLength = sizeof(poi->ObjectId); uId.Buffer = (PWSTR) poi->ObjectId; InitializeObjectAttributes( &oa, &uId, OBJ_CASE_INSENSITIVE, _hVolume, NULL ); // ----------------- // Open the old file // ----------------- // Some kind of write access, along with restore privelege, is required // for set/delete OID calls. EnableRestorePrivilege(); Status = NtCreateFile( &hFile, SYNCHRONIZE | FILE_WRITE_ATTRIBUTES, &oa, &ios, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_BY_FILE_ID | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_NO_RECALL | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); // ------------------------------ // Delete the OID on the old file // ------------------------------ if (NT_SUCCESS(Status)) { Status = NtFsControlFile( hFile, NULL, NULL, NULL, &ios, FSCTL_DELETE_OBJECT_ID, NULL, // in buffer 0, // in buffer size NULL, // Out buffer 0); // Out buffer size NtClose( hFile ); hFile = NULL; if (NT_SUCCESS(Status)) { TrkLog((TRKDBG_TUNNEL, TEXT("Tunnelling objid %s - deleted from old file"), (const TCHAR*)CDebugString(objid) )); } else { TrkLog((TRKDBG_TUNNEL, TEXT("Tunnelling objid %c:%s - couldn't FSCTL_DELETE_OBJECT_ID ntstatus=%08x"), VolChar(_iVol), (const TCHAR*)CDebugString(objid), Status )); } } // if (NT_SUCCESS(Status)) else { // We couldn't open the old file, so we'll ignore it and try to set the // object ID. TrkLog((TRKDBG_TUNNEL, TEXT("Tunnelling objid %c:%s - couldn't open old file %08x"), VolChar(_iVol), (const TCHAR*)CDebugString(objid), Status)); } if( Status == STATUS_INVALID_DEVICE_REQUEST || IsErrorDueToLockedVolume( Status ) ) { // If we get STATUS_INVALID_DEVICE_REQUEST, then _hVolume is // broken. CloseVolumeHandles(); g_ptrkwks->SetReopenVolumeHandlesTimer(); __leave; } // ----------------- // Open the new file // ----------------- uId.Length = sizeof(poi->FileReference); uId.MaximumLength = sizeof(poi->FileReference); uId.Buffer = (PWSTR) &poi->FileReference; Status = NtCreateFile( &hFile, SYNCHRONIZE | FILE_WRITE_ATTRIBUTES, &oa, &ios, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_BY_FILE_ID | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_NO_RECALL | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); // --------------------------- // Set the OID on the new file // --------------------------- if (NT_SUCCESS(Status)) { Status = NtFsControlFile( hFile, NULL, NULL, NULL, &ios, FSCTL_SET_OBJECT_ID, poi->ObjectId, sizeof(FILE_OBJECTID_BUFFER), NULL, // Out buffer 0); // Out buffer size NtClose(hFile); hFile = NULL; TrkLog((TRKDBG_TUNNEL, TEXT("Tunnelling objid %s: FSCTL_SET_OBJECT_ID %s %08x"), (const TCHAR*)CDebugString(objid), NT_SUCCESS(Status) ? TEXT("succeeded") : TEXT("failed"), Status )); } else { TrkLog((TRKDBG_TUNNEL, TEXT("Tunnelling objid %c:%s - couldn't OpenByFileReference ntstatus=%08x"), VolChar(_iVol), (const TCHAR*)CDebugString(objid), Status )); } if(Status == STATUS_INVALID_DEVICE_REQUEST || IsErrorDueToLockedVolume( Status ) ) { // If we get STATUS_INVALID_DEVICE_REQUEST, then _hVolume is // broken. CloseVolumeHandles(); g_ptrkwks->SetReopenVolumeHandlesTimer(); __leave; } } __except( BreakOnDebuggableException() ) { TrkLog(( TRKDBG_ERROR, TEXT("Exception %08x in CVolume::FileActionIdNotTunnelled"), GetExceptionCode() )); } if( NULL != hFile ) NtClose( hFile ); return; } //+---------------------------------------------------------------------------- // // CVolume::NotifyAddOrDelete // // This method is called as an event notification, indicating that NTFS has // notified us that the volume ID has been modified. We use this to ensure // the volume ID doesn't get incorrectly modified. // // If you watch the object ID notification queue while someone sets the // volume ID directly in NTFS, you'll see: // * a remove of the old ID (setting a new ID shows up as a remove/add), // * an add of the bogus ID, // * a remove of the bogus ID (part of the SetVolid that we do in this routine), // * an add of the correct ID. // //+---------------------------------------------------------------------------- void CVolume::NotifyAddOrDelete( ULONG Action, const CObjId & objid ) { CVolumeId volidCorrect; // We don't take the volume lock here. So don't attempt to // do anything other than simple I/O. We don't take the lock because // we want to ensure the notifications from NTFS don't get backed up // (so we don't miss any tunnel notifications). // We only hook removes if( FILE_ACTION_REMOVED != Action ) return; volidCorrect = GetVolumeId(); if( volidCorrect == objid && !_fInSetVolIdOnVolume ) { NTSTATUS status = 0; status = SetVolIdOnVolume( volidCorrect ); TrkLog(( TRKDBG_WARNING|TRKDBG_VOLUME, TEXT("Undoing delete of volume ID:\n => %s (%08x)"), (const TCHAR*)CDebugString( volidCorrect ), status )); } return; } //+---------------------------------------------------------------------------- // // CVolume::LoadVolInfo // // Load the _volinfo member from the log. // //+---------------------------------------------------------------------------- void CVolume::LoadVolInfo() { AssertLocked(); TrkAssert( CVOLUME_HEADER_LENGTH == sizeof(_volinfo) ); // Read _volinfo from the extended header portion of the log. __try { _cLogFile.ReadExtendedHeader( CVOLUME_HEADER_START, &_volinfo, sizeof(_volinfo) ); } __except( IsRecoverableDiskError( GetExceptionCode() ) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { // Attempt to recover from this error and retry the read. if( IsErrorDueToLockedVolume( GetExceptionCode() )) { CloseAndReopenVolumeHandles(); _cLogFile.ReadExtendedHeader( CVOLUME_HEADER_START, &_volinfo, sizeof(_volinfo) ); } else { TrkAssert( TRK_E_CORRUPT_LOG == GetExceptionCode() ); TrkReportEvent( EVENT_TRK_SERVICE_CORRUPT_LOG, EVENTLOG_ERROR_TYPE, static_cast( CStringize( VolChar(_iVol) )), NULL ); DeleteLogAndReInitializeVolume(); } } } //+---------------------------------------------------------------------------- // // CVolume::SaveVolInfo // // Write the _volinfo structure to the extended header portion of the log. // Clear _fDirty if successful. // //+---------------------------------------------------------------------------- void CVolume::SaveVolInfo( ) { AssertLocked(); __try { _cLogFile.WriteExtendedHeader( CVOLUME_HEADER_START, &_volinfo, sizeof(_volinfo) ); _fDirty = FALSE; } __except( IsRecoverableDiskError( GetExceptionCode() ) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { // Attempt to recover from this error and retry the write. if( IsErrorDueToLockedVolume( GetExceptionCode() ) ) { CloseAndReopenVolumeHandles(); _cLogFile.WriteExtendedHeader( CVOLUME_HEADER_START, &_volinfo, sizeof(_volinfo) ); _fDirty = FALSE; } else { TrkAssert( TRK_E_CORRUPT_LOG == GetExceptionCode() ); TrkReportEvent( EVENT_TRK_SERVICE_CORRUPT_LOG, EVENTLOG_ERROR_TYPE, static_cast( CStringize( VolChar(_iVol) )), NULL ); DeleteLogAndReInitializeVolume(); } } } //+---------------------------------------------------------------------------- // // CVolume::CloseVolumeHandles // // This method close all handle that this object maintains on the volume. // This will allow e.g. format or chkdsk /f to run successfully. // We don't take the volume critsec, so we're guaranteed to run quickly // and not block. // //+---------------------------------------------------------------------------- void CVolume::CloseVolumeHandles( HDEVNOTIFY hdnVolume, EHandleChangeReason eHandleChangeReason ) { HANDLE hVolToClose = NULL; // This routine never raises // Is this notification intended for everyone, or specifically // for us? if( hdnVolume != NULL && hdnVolume != _hdnVolumeLock ) // No, it's just for another volume. return; // Are we already in a CloseVolumeHandles somewhere? // If so, there's no need to continue, and worse yet if we were // to continue we could deadlock (scenario: one thread is in // _cLogFile.Close below unregistering the oplock wait, which is // blocking, and another thread is executing an oplock break). if( !BeginSingleInstanceTask( &_cCloseVolumeHandlesInProgress ) ) { TrkLog(( TRKDBG_VOLUME, TEXT("Skipping CloseVolumeHandles, another instance already in progress") )); return; } // We don't want this method to take the normal _csVolume lock, because // we're called from threads that must not block (such as the service control handler // thread). So we just take the limited lock that's used in this method // and in ReopenVolumeHandles. LockHandles(); __try { BOOL fAlreadyLockedOrDismounted = _fVolumeLocked || _fVolumeDismounted; TrkLog(( TRKDBG_WARNING, TEXT("Closing volume handles on %c:"), VolChar(_iVol) )); // If this notification is specifically for us, then remember // if we're locked/dismounted. if( hdnVolume != NULL ) { if( VOLUME_LOCK_CHANGE == eHandleChangeReason ) _fVolumeLocked = TRUE; else if( VOLUME_MOUNT_CHANGE == eHandleChangeReason ) _fVolumeDismounted = TRUE; } // Is this volume already locked or dismounted? if( fAlreadyLockedOrDismounted ) { TrkAssert( NULL == _hVolume ); __leave; } // Close the object ID index directory handle. _ObjIdIndexChangeNotifier.StopListeningAndClose(); // Close the log. _cLogFile.Close(); // Doesn't raise // Prepare to close the volume handle. if (_hVolume != NULL) { hVolToClose = _hVolume; _hVolume = NULL; } TrkLog((TRKDBG_VOLUME, TEXT("Volume %c: closed"), VolChar(_iVol))); } __except( EXCEPTION_EXECUTE_HANDLER ) // BreakThenReturn( EXCEPTION_EXECUTE_HANDLER )) { TrkLog(( TRKDBG_ERROR, TEXT("Ignoring unexpected exception in CVolume::CloseVolumeHandles (%08x)"), GetExceptionCode() )); } UnlockHandles(); EndSingleInstanceTask( &_cCloseVolumeHandlesInProgress ); if( NULL != hVolToClose ) { NtClose( hVolToClose ); TrkLog((TRKDBG_VOLUME, TEXT("(Volume %c: fully closed)"), VolChar(_iVol))); } } //+---------------------------------------------------------------------------- // // CVolume::ReopenVolumeHandles // // Reopen the handles that we maintain on the volume. This is synchronized // with CloseVolumeHandles using the handle critsec. This method does // nothing if the volume is locked (as indicated by _fVolumeLocked), // or if the volume handles are already opened. // //+---------------------------------------------------------------------------- BOOL CVolume::ReopenVolumeHandles() { NTSTATUS status; BOOL fHandlesLocked = FALSE; BOOL fHandlesOpen = FALSE; BOOL fReopenedLog = FALSE; BOOL fStartedListening = FALSE; // Don't open if the service is stopping. g_ptrkwks->RaiseIfStopped(); TrkLog(( TRKDBG_WARNING, TEXT("\nReopenVolumeHandles called on %c:"), VolChar(_iVol) )); // This method must acquire the _csVolumes critical section like every other // public method (via the Lock call). It must also acquire the _csHandles // lock, in order to coordinate with the CloseVolumeHandles and SetUnlockVolume // methods, which have special needs. Lock(); LockHandles(); fHandlesLocked = TRUE; __try { // Are we supposed to reopen? if( IsHandsOffVolumeMode() ) { // Don't open yet, wait until we get an UnLock notification. TrkLog(( TRKDBG_VOLUME, TEXT("Didn't open handles on %c:, it's %s"), VolChar(_iVol), _fVolumeLocked ? ( _fVolumeDismounted ? TEXT("locked & dismounted") : TEXT("locked") ) : TEXT("dismounted") )); __leave; } // Open the main volume handle. if( NULL == _hVolume ) { status = OpenVolume(_tszVolumeDeviceName, &_hVolume); if(!NT_SUCCESS(status)) { if( STATUS_OBJECT_NAME_NOT_FOUND == status ) { TrkLog(( TRKDBG_VOLUME, TEXT("Volume not found in ReopenVolumeHandles, deleting CVolume") )); MarkSelfForDelete(); } TrkRaiseNtStatus(status); } } // Start listening for objid index change notifications __try { fStartedListening = _ObjIdIndexChangeNotifier.AsyncListen( ); } __except( BreakOnDebuggableException() ) { // We should never get a path-not-found error, because meta-files always exist // on a good NTFS5 volume. If we get one, it's probably because an NTFS5 // volume has been reformatted as a FAT volume. HRESULT hr = GetExceptionCode(); if( HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr ) { TrkLog(( TRKDBG_VOLUME, TEXT("ObjId Index not found in ReopenVolumeHandles, deleting CVolume") )); MarkSelfForDelete(); } TrkRaiseException( hr ); } // Register for PNP notifications. We don't hold the handle lock because this // call can take a while, and we don't want to block the service control // handler thread from doing a CloseVolumeHandles. if( fStartedListening ) { #if DBG TrkVerify( 0 == UnlockHandles() ); #else UnlockHandles(); #endif fHandlesLocked = FALSE; RegisterPnPVolumeNotification(); LockHandles(); fHandlesLocked = TRUE; } // If CloseVolumeHandles came in after we released the handle lock just now, // then abort. if( NULL == _hVolume ) { TrkLog(( TRKDBG_VOLUME, TEXT("Aborting ReopenVolumeHandles") )); TrkRaiseException( E_FAIL ); } // Open the log if( !_cLogFile.IsOpen() ) { for( int i = 0; i < 2; i++ ) { __try { _cLogFile.Initialize( _tszVolumeDeviceName, _pTrkWksConfiguration, this, VolChar(_iVol) ); _cLog.Initialize( _pLogCallback, _pTrkWksConfiguration, &_cLogFile ); } __except( (0 == i && TRK_E_CORRUPT_LOG == GetExceptionCode()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { BreakIfRequested(); // Get rid of the corrupt file _cLogFile.Delete(); // Loop back and try again. continue; } break; } fReopenedLog = TRUE; } // If we've never read in the volinfo, do so now. if( !_fVolInfoInitialized ) { LoadVolInfo(); _fVolInfoInitialized = TRUE; TrkLog(( TRKDBG_VOLUME, TEXT("VolId (from log file) for %c: is %s"), VolChar(_iVol), (const TCHAR*)CDebugString(_volinfo.volid) )); } if( fReopenedLog ) { // Reconcile our volinfo with the log. // BUGBUG (removable media): We haven't re-read the volinfo out of the log, we're still // using what we already had in memory. This won't work for removeable // media, so we need to add some extra checking here. VolumeSanityCheck(); // Start the move notify timer; we may have been trying to send // notifies when we discovered that the volume handles were bad. g_ptrkwks->OnEntriesAvailable(); } TrkLog((TRKDBG_VOLUME, TEXT("Volume %c open"), VolChar(_iVol))); fHandlesOpen = TRUE; } __finally { if( fHandlesLocked ) UnlockHandles(); // We either open everything, or open nothing if( AbnormalTermination() ) { CloseVolumeHandles(); // Doesn't raise g_ptrkwks->SetReopenVolumeHandlesTimer(); // Try again later } Unlock(); } return( fHandlesOpen ); } //+---------------------------------------------------------------------------- // // CVolume::CloseAndReopenVolumeHandles // // One or more of the handles maintained on the volume are bad (for example, // the handle may have been broken by a dismount). Close all of them, and // attempt to reopen new ones. // //+---------------------------------------------------------------------------- void CVolume::CloseAndReopenVolumeHandles() { Lock(); __try { // There's the remote possibility that the ReopenVolumeHandles // call below will call this routine. Just to be paranoid, we // add protection against an infinite recursion. if( _fCloseAndReopenVolumeHandles ) TrkRaiseWin32Error( ERROR_OPEN_FAILED ); _fCloseAndReopenVolumeHandles = TRUE; // Close then reopen the handles CloseVolumeHandles(); ReopenVolumeHandles(); } __except( BreakOnDebuggableException() ) { TrkLog((TRKDBG_VOLUME, TEXT("Immediate reopen of volume handle failed, set the reopen timer"))); g_ptrkwks->SetReopenVolumeHandlesTimer(); } _fCloseAndReopenVolumeHandles = FALSE; Unlock(); } //+---------------------------------------------------------------------------- // // CVolume::PrepareToReopenVolumeHandles // // The handles to the volume were closed at some point, but they may now // be reopened. Call CVolumeManager::OnVolumeToBeReopened, so that it can // call us in ReopenVolumeHandles on a worker thread (right now we're on // the services handler thread, which is shared by all of services.exe). // //+---------------------------------------------------------------------------- void CVolume::PrepareToReopenVolumeHandles( HDEVNOTIFY hdnVolume, EHandleChangeReason eHandleChangeReason ) { // It is important that this method does not take the volume lock. // This method is called during the volume unlock notification // that we receive in CTrkWksSvc::ServiceHandler, and we can // never allow that thread to hang. LockHandles(); if( _hdnVolumeLock == hdnVolume ) { if( VOLUME_LOCK_CHANGE == eHandleChangeReason ) _fVolumeLocked = FALSE; else if( VOLUME_MOUNT_CHANGE == eHandleChangeReason ) _fVolumeDismounted = FALSE; if( !_fVolumeLocked && !_fVolumeDismounted ) { TrkLog(( TRKDBG_VOLUME, TEXT("Volume %c: is to be reopened"), VolChar(_iVol) )); _pVolMgr->OnVolumeToBeReopened(); } } UnlockHandles(); return; }