windows-nt/Source/XPSP1/NT/com/svcdlls/trksvcs/trkwks/vol.cxx

2759 lines
83 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
// 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 <pch.cxx>
#pragma hdrstop
#include "trkwks.hxx"
#include <dbt.h>
#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<const TCHAR*>(CStringize( VolChar(GetIndex()))),
static_cast<const TCHAR*>(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<const TCHAR*>( 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<const TCHAR*>( 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<const TCHAR*>( CStringize( VolChar(_iVol) )),
static_cast<const TCHAR*>( 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<const TCHAR*>( CStringize( VolChar(_iVol) )),
static_cast<const TCHAR*>( 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<const TCHAR*>( 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<const TCHAR*>( 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<const TCHAR*>( 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<const TCHAR*>( 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<const TCHAR*>( 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<ULONG>((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<const TCHAR*>( 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<const TCHAR*>( 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<const TCHAR*>( 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;
}