You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
407 lines
14 KiB
407 lines
14 KiB
|
|
// Copyright (c) 1996-1999 Microsoft Corporation
|
|
|
|
//+============================================================================
|
|
//
|
|
// oidindex.cxx
|
|
//
|
|
// Implementation of CObjIdIndexChangeNotifier, which moniters the
|
|
// object ID index for changes. When a change is received, the
|
|
// CVolume is notified.
|
|
//
|
|
//+============================================================================
|
|
|
|
#include <pch.cxx>
|
|
#pragma hdrstop
|
|
#include "trkwks.hxx"
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CObjIdIndexChangeNotifier::DoWork
|
|
//
|
|
// Called by the work manager - on the primary thread - when we've received
|
|
// a notification from the object ID index.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
void
|
|
CObjIdIndexChangeNotifier::DoWork()
|
|
{
|
|
LPBYTE pbScan = _Buffer;
|
|
FILE_NOTIFY_INFORMATION * pNotifyInfo;
|
|
|
|
// Get the size and ntstatus of the notification.
|
|
|
|
DWORD dwNumberOfBytesTransfered = static_cast<DWORD>(_Overlapped.InternalHigh);
|
|
NTSTATUS status = static_cast<NTSTATUS>(_Overlapped.Internal);
|
|
|
|
_cs.Enter();
|
|
__try // __except
|
|
{
|
|
// Is this a good notification?
|
|
|
|
if( NT_SUCCESS(status) )
|
|
{
|
|
// Did we get data in this notification?
|
|
if( dwNumberOfBytesTransfered >= sizeof(FILE_NOTIFY_INFORMATION) )
|
|
{
|
|
// Yes. Loop through the entries, calling to special handlers
|
|
// for delete notifications and tunnelling-failure notifications.
|
|
|
|
do
|
|
{
|
|
pNotifyInfo = (FILE_NOTIFY_INFORMATION*)pbScan;
|
|
FILE_OBJECTID_INFORMATION *poi = (FILE_OBJECTID_INFORMATION*) pNotifyInfo->FileName;
|
|
|
|
TrkLog((TRKDBG_OBJID_DELETIONS,
|
|
TEXT("NTFS ObjId Index: %s"),
|
|
(const TCHAR*)CDebugString( _pVolume->GetVolIndex(),
|
|
pNotifyInfo) ));
|
|
|
|
// Check for adds/deletes
|
|
|
|
if (pNotifyInfo->Action == FILE_ACTION_REMOVED_BY_DELETE
|
|
||
|
|
pNotifyInfo->Action == FILE_ACTION_ADDED)
|
|
{
|
|
// Notify the general add/deletions handler
|
|
|
|
CDomainRelativeObjId droidBirth( *poi );
|
|
_pObjIdIndexChangedCallback->NotifyAddOrDelete( pNotifyInfo->Action, droidBirth );
|
|
}
|
|
|
|
// Check for tunnelling notifications
|
|
else
|
|
if (pNotifyInfo->Action == FILE_ACTION_ID_NOT_TUNNELLED)
|
|
{
|
|
// An attempt to tunnel an object ID failed because another file on the
|
|
// same volume was already using it.
|
|
_pVolume->FileActionIdNotTunnelled( (FILE_OBJECTID_INFORMATION*) pNotifyInfo->FileName );
|
|
}
|
|
|
|
} while ( pNotifyInfo->NextEntryOffset != 0 &&
|
|
( pbScan += pNotifyInfo->NextEntryOffset) );
|
|
} // if( dwNumberOfBytesTransfered >= sizeof(FILE_NOTIFY_INFORMATION) )
|
|
|
|
// We didn't get any data. Is this notification telling us that the IRP was
|
|
// cancelled?
|
|
|
|
else if( STATUS_NOTIFY_CLEANUP == status )
|
|
{
|
|
TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("OverlappedCompletionRoutine on %c: cleaning up"),
|
|
VolChar( _pVolume->GetIndex() ) ));
|
|
}
|
|
} // if( NT_SUCCESS(status) )
|
|
|
|
else if( STATUS_CANCELLED == status )
|
|
{
|
|
// The thread on which we made the ReadDirectoryChanges call terminated,
|
|
// thus terminating our IRP. We should now be running on an IO thread, since
|
|
// we register with WT_EXECUTEINIOTHREAD, so we just fall through and
|
|
// re-issue the IRP.
|
|
|
|
TrkLog(( TRKDBG_OBJID_DELETIONS,
|
|
TEXT("OverlappedCompletionRoutine on %c: ignoring status_cancelled"),
|
|
VolChar( _pVolume->GetIndex() ) ));
|
|
}
|
|
else
|
|
{
|
|
// If we failed for any other reason, there's something wrong. We don't
|
|
// want to call ReadDirectoryChanges again, because it might give us the
|
|
// same failure right away, and we'd thus be in an infinite loop.
|
|
|
|
TrkLog(( TRKDBG_ERROR, TEXT("OverlappedCompletionRoutine on %c: aborting due to %08x"),
|
|
status ));
|
|
CloseHandle( _hDir );
|
|
_hDir = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
// When StopListeningAndClose is called, CancelIo is called, which triggers
|
|
// this DoWork routine. But we don't run until we get the critical section,
|
|
// after which time _hDir will be invalid.
|
|
|
|
if( INVALID_HANDLE_VALUE != _hDir )
|
|
{
|
|
StartListening();
|
|
}
|
|
|
|
}
|
|
__except( EXCEPTION_EXECUTE_HANDLER )
|
|
{
|
|
// We should never get any kind of an error here. If we do, the simplest
|
|
// recourse is just to reinit the volume.
|
|
|
|
_pVolume->OnHandlesMustClose();
|
|
}
|
|
|
|
_cs.Leave();
|
|
|
|
return;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CObjIdIndexChangeNotifier::Initialize
|
|
//
|
|
// Initialize the critical section and register with the work manager.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
void
|
|
CObjIdIndexChangeNotifier::Initialize(
|
|
TCHAR *ptszVolumeDeviceName,
|
|
PObjIdIndexChangedCallback * pObjIdIndexChangedCallback,
|
|
CVolume * pVolumeForTunnelNotification
|
|
)
|
|
{
|
|
TrkAssert( !_fInitialized );
|
|
|
|
_cs.Initialize();
|
|
_fInitialized = TRUE;
|
|
|
|
_ptszVolumeDeviceName = ptszVolumeDeviceName;
|
|
_pObjIdIndexChangedCallback = pObjIdIndexChangedCallback;
|
|
_hDir = INVALID_HANDLE_VALUE;
|
|
_pVolume = pVolumeForTunnelNotification;
|
|
|
|
TrkAssert( NULL == _hCompletionEvent );
|
|
_hCompletionEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); // Auto-reset, not signaled
|
|
if( NULL == _hCompletionEvent )
|
|
{
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't create completion event for objid index change notify (%lu)"),
|
|
GetLastError() ));
|
|
TrkRaiseLastError();
|
|
}
|
|
|
|
// Register the completion event with the thread pool.
|
|
|
|
_hRegisterWaitForSingleObjectEx
|
|
= TrkRegisterWaitForSingleObjectEx( _hCompletionEvent, ThreadPoolCallbackFunction,
|
|
static_cast<PWorkItem*>(this), INFINITE,
|
|
WT_EXECUTEINWAITTHREAD );
|
|
if( NULL == _hRegisterWaitForSingleObjectEx )
|
|
{
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Failed RegisterWaitForSingleObjectEx in CObjIdIndexChangeNotifier (%lu) for %s"),
|
|
GetLastError(), ptszVolumeDeviceName ));
|
|
TrkRaiseLastError();
|
|
}
|
|
else
|
|
TrkLog(( TRKDBG_VOLUME, TEXT("Registered objid index change notification (%p)"),
|
|
_hRegisterWaitForSingleObjectEx ));
|
|
}
|
|
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CObjIdIndexChangeNotifier::StartListening
|
|
//
|
|
// Call ReadDirectoryChangesW on the handle to the object ID index.
|
|
// This is an event-based async call, so it returns immediately, and
|
|
// NTFS signals the event when there's a notification ready.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
void
|
|
CObjIdIndexChangeNotifier::StartListening()
|
|
{
|
|
// NTFS will write the notification into the _Overlapped structure.
|
|
|
|
memset(&_Overlapped, 0, sizeof(_Overlapped));
|
|
_Overlapped.hEvent = _hCompletionEvent;
|
|
_Overlapped.Internal = STATUS_INTERNAL_ERROR;
|
|
|
|
if (!ReadDirectoryChangesW( _hDir,
|
|
_Buffer, // pointer to the buffer to receive the read results
|
|
sizeof(_Buffer), // length of lpBuffer
|
|
FALSE, // flag for monitoring directory or directory tree
|
|
FILE_NOTIFY_CHANGE_FILE_NAME, // filter conditions to watch for
|
|
&_dwDummyBytesReturned, // number of bytes returned
|
|
&_Overlapped, // pointer to structure needed for overlapped I/O
|
|
NULL )) // pointer to completion routine
|
|
{
|
|
CloseHandle(_hDir);
|
|
_hDir = INVALID_HANDLE_VALUE;
|
|
|
|
TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("AsyncListen failed to ReadDirectoryChanges %d"),
|
|
GetLastError() ));
|
|
TrkRaiseLastError();
|
|
}
|
|
|
|
// Ordinarily, the previous call will leave an IO pending. If, however,
|
|
// it actually returns right away with data, set the event as if the data came
|
|
// back async.
|
|
|
|
if( GetOverlappedResult( _hDir, &_Overlapped, &_dwDummyBytesReturned, FALSE /*Don't Wait*/ ))
|
|
{
|
|
// There was data immediately available. Handle it in the normal way.
|
|
TrkVerify( SetEvent( _Overlapped.hEvent ));
|
|
}
|
|
else if( ERROR_IO_INCOMPLETE != GetLastError() ) // STATUS_PENDING
|
|
{
|
|
// This should never occur
|
|
TrkLog(( TRKDBG_ERROR, TEXT("GetOverlappedResult failed in CObjIdIndexChangeNotifier::AsyncListen (%lu)"),
|
|
GetLastError() ));
|
|
TrkRaiseLastError();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CObjIdIndexChangeNotifier::AsyncListen
|
|
//
|
|
// This method begins listening for changes to the NTFS object ID index
|
|
// directory. It does not block; when notifications are available an
|
|
// event is signaled and handled in DoWork.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
|
|
BOOL
|
|
CObjIdIndexChangeNotifier::AsyncListen( )
|
|
{
|
|
TCHAR tszDirPath[MAX_PATH];
|
|
BOOL fStartedListening = FALSE;
|
|
|
|
_cs.Enter();
|
|
__try // __finally
|
|
{
|
|
if( INVALID_HANDLE_VALUE != _hDir )
|
|
{
|
|
TrkLog(( TRKDBG_OBJID_DELETIONS, TEXT("CObjIdIndexChangeNotifier already listening to %s:"),
|
|
_ptszVolumeDeviceName ));
|
|
__leave;
|
|
}
|
|
|
|
_tcscpy( tszDirPath, _ptszVolumeDeviceName );
|
|
_tcscat( tszDirPath, TEXT("\\$Extend\\$ObjId:$O:$INDEX_ALLOCATION") );
|
|
|
|
//
|
|
// Should use TrkCreateFile and NtNotifyChangeDirectoryFile
|
|
// but NtNotifyChangeDirectoryFile means writing an APC routine
|
|
// so I'm punting for now.
|
|
// None of these Win32 error codess need to be raised to the user.
|
|
//
|
|
|
|
_hDir = CreateFile (
|
|
tszDirPath,
|
|
FILE_LIST_DIRECTORY,
|
|
FILE_SHARE_WRITE|FILE_SHARE_READ|FILE_SHARE_DELETE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
|
NULL );
|
|
|
|
if (_hDir == INVALID_HANDLE_VALUE)
|
|
{
|
|
TrkLog((TRKDBG_OBJID_DELETIONS,
|
|
TEXT("AsyncListen failed to open objid index %s %d"),
|
|
tszDirPath,
|
|
GetLastError()));
|
|
|
|
TrkRaiseLastError();
|
|
}
|
|
|
|
StartListening(); // Call ReadDirectoryChangesW
|
|
fStartedListening = TRUE;
|
|
|
|
TrkLog((TRKDBG_OBJID_DELETIONS,
|
|
TEXT("AsyncListen succeeded ReadDirectoryChanges on %c:"), VolChar(_pVolume->GetIndex()) ));
|
|
|
|
|
|
}
|
|
__finally
|
|
{
|
|
_cs.Leave();
|
|
}
|
|
|
|
return fStartedListening;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CObjIdIndexChangeNotifier::StopListeningAndClose
|
|
//
|
|
// Cancel the previous call to ReadDirectoryChangesW, and close the
|
|
// handle to the object ID index directory.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
void
|
|
CObjIdIndexChangeNotifier::StopListeningAndClose()
|
|
{
|
|
if( !_fInitialized )
|
|
return;
|
|
|
|
_cs.Enter();
|
|
|
|
TrkLog((TRKDBG_OBJID_DELETIONS, TEXT("StopListeningAndClose() on %c:"),
|
|
VolChar(_pVolume->GetIndex())));
|
|
|
|
// Cancel the IO, which will trigger once last completion with
|
|
// STATUS_NOTIFY_CLEANUP (why isn't it STATUS_CANCELLED?)
|
|
// Note that this one last completion will see that _hDir
|
|
// has been closed, and won't attempt to re-use it.
|
|
|
|
InterlockedCloseHandle( &_hDir, INVALID_HANDLE_VALUE, TRUE );
|
|
|
|
_cs.Leave();
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CObjIdIndexChangeNotifier::UnInitialize
|
|
//
|
|
// Cancel the notification IRP and close the handle to the
|
|
// object ID index directory.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
void
|
|
CObjIdIndexChangeNotifier::UnInitialize()
|
|
{
|
|
if( _fInitialized )
|
|
{
|
|
StopListeningAndClose();
|
|
|
|
// Unregister from the thread pool. This must be done before closing
|
|
// _hCompletionEvent, because that's the event on which the thread
|
|
// pool is waiting.
|
|
|
|
if( NULL != _hRegisterWaitForSingleObjectEx )
|
|
{
|
|
if( !TrkUnregisterWait( _hRegisterWaitForSingleObjectEx ))
|
|
{
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Failed UnregisterWait for CObjIdIndexChangeNotifier (%lu)"),
|
|
GetLastError() ));
|
|
}
|
|
else
|
|
TrkLog(( TRKDBG_VOLUME, TEXT("Unregistered wait for CObjIdIndexChangeNotifier (%p)"),
|
|
_hRegisterWaitForSingleObjectEx ));
|
|
|
|
_hRegisterWaitForSingleObjectEx = NULL;
|
|
}
|
|
|
|
if( NULL != _hCompletionEvent )
|
|
{
|
|
CloseHandle( _hCompletionEvent );
|
|
_hCompletionEvent = NULL;
|
|
}
|
|
|
|
// Delete the critical section. This must be done after unregistering from
|
|
// the thread pool, because until that time we have to worry about a thread
|
|
// coming in to DoWork.
|
|
|
|
_cs.UnInitialize();
|
|
_fInitialized = FALSE;
|
|
}
|
|
}
|
|
|
|
|