/*++

Copyright (c) 1989-1999  Microsoft Corporation

Module Name:

    fspyLib.c

Abstract:

    This contains library support routines for FileSpy.  These routines
    do the main work for logging the I/O operations --- creating the log
    records, recording the relevant information, attach/detach from
    devices, etc.

    As of the Windows XP SP1 IFS Kit version of this sample and later, this
    sample can be built for each build environment released with the IFS Kit
    with no additional modifications.  To provide this capability, additional
    compile-time logic was added -- see the '#if WINVER' locations.  Comments
    tagged with the 'VERSION NOTE' header have also been added as appropriate to
    describe how the logic must change between versions.

    If this sample is built in the Windows XP environment or later, it will run
    on Windows 2000 or later.  This is done by dynamically loading the routines
    that are only available on Windows XP or later and making run-time decisions
    to determine what code to execute.  Comments tagged with 'MULTIVERISON NOTE'
    mark the locations where such logic has been added.

Environment:

    Kernel mode

--*/

#include <stdio.h>

#include <..\ifs\wxp\ntifs.h>
#include "filespy.h"
#include "fspyKern.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, SpyReadDriverParameters)
#pragma alloc_text(PAGE, SpyAttachDeviceToDeviceStack)
#pragma alloc_text(PAGE, SpyQueryFileSystemForFileName)
#pragma alloc_text(PAGE, SpyQueryInformationFile)
#pragma alloc_text(PAGE, SpyIsAttachedToDeviceByUserDeviceName)
#pragma alloc_text(PAGE, SpyIsAttachedToDevice)
#pragma alloc_text(PAGE, SpyIsAttachedToDeviceW2K)
#pragma alloc_text(PAGE, SpyAttachToMountedDevice)
#pragma alloc_text(PAGE, SpyCleanupMountedDevice)
#pragma alloc_text(PAGE, SpyAttachToDeviceOnDemand)
#pragma alloc_text(PAGE, SpyAttachToDeviceOnDemandW2K)
#pragma alloc_text(PAGE, SpyStartLoggingDevice)
#pragma alloc_text(PAGE, SpyStopLoggingDevice)
#pragma alloc_text(PAGE, SpyAttachToFileSystemDevice)
#pragma alloc_text(PAGE, SpyDetachFromFileSystemDevice)
#pragma alloc_text(PAGE, SpyGetAttachList)
#pragma alloc_text(PAGE, SpyGetObjectName)


#if WINVER >= 0x0501
#pragma alloc_text(INIT, SpyLoadDynamicFunctions)
#pragma alloc_text(INIT, SpyGetCurrentVersion)
#pragma alloc_text(PAGE, SpyIsAttachedToDeviceWXPAndLater)
#pragma alloc_text(PAGE, SpyAttachToDeviceOnDemandWXPAndLater)
#pragma alloc_text(PAGE, SpyEnumerateFileSystemVolumes)
#pragma alloc_text(PAGE, SpyGetBaseDeviceObjectName)
#endif

#endif

//////////////////////////////////////////////////////////////////////////
//                                                                      //
//                     Library support routines                         //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

VOID
SpyReadDriverParameters (
    IN PUNICODE_STRING RegistryPath
    )
/*++

Routine Description:

    This routine tries to read the FileSpy-specific parameters from
    the registry.  These values will be found in the registry location
    indicated by the RegistryPath passed in.

Arguments:

    RegistryPath - the path key which contains the values that are
        the FileSpy parameters

Return Value:

    None.

--*/
{
    OBJECT_ATTRIBUTES attributes;
    HANDLE driverRegKey;
    NTSTATUS status;
    ULONG bufferSize, resultLength;
    PVOID buffer = NULL;
    UNICODE_STRING valueName;
    PKEY_VALUE_PARTIAL_INFORMATION pValuePartialInfo;

    PAGED_CODE();

    //
    //  All the global values are already set to default values.  Any
    //  values we read from the registry will override these defaults.
    //
    
    //
    //  Do the initial setup to start reading from the registry.
    //

    InitializeObjectAttributes( &attributes,
								RegistryPath,
								OBJ_CASE_INSENSITIVE,
								NULL,
								NULL);

    status = ZwOpenKey( &driverRegKey,
						KEY_READ,
						&attributes);

    if (!NT_SUCCESS(status)) {

        driverRegKey = NULL;
        goto SpyReadDriverParameters_Exit;
    }

    bufferSize = sizeof( KEY_VALUE_PARTIAL_INFORMATION ) + sizeof( ULONG );
    buffer = ExAllocatePoolWithTag( NonPagedPool, bufferSize, FILESPY_POOL_TAG );

    if (NULL == buffer) {

        goto SpyReadDriverParameters_Exit;
    }

    //
    // Read the gMaxRecordsToAllocate from the registry
    //

    RtlInitUnicodeString(&valueName, MAX_RECORDS_TO_ALLOCATE);

    status = ZwQueryValueKey( driverRegKey,
							  &valueName,
							  KeyValuePartialInformation,
							  buffer,
							  bufferSize,
							  &resultLength);

    if (NT_SUCCESS(status)) {

        pValuePartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION) buffer;
        ASSERT(pValuePartialInfo->Type == REG_DWORD);
        gMaxRecordsToAllocate = *((PLONG)&pValuePartialInfo->Data);

    }

    //
    // Read the gMaxNamesToAllocate from the registry
    //

    RtlInitUnicodeString(&valueName, MAX_NAMES_TO_ALLOCATE);

    status = ZwQueryValueKey( driverRegKey,
							  &valueName,
							  KeyValuePartialInformation,
							  buffer,
							  bufferSize,
							  &resultLength);

    if (NT_SUCCESS(status)) {

        pValuePartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION) buffer;
        ASSERT(pValuePartialInfo->Type == REG_DWORD);
        gMaxNamesToAllocate = *((PLONG)&pValuePartialInfo->Data);

    }

    //
    // Read the initial debug setting from the registry
    //

    RtlInitUnicodeString(&valueName, DEBUG_LEVEL);

    status = ZwQueryValueKey( driverRegKey,
                              &valueName,
                              KeyValuePartialInformation,
                              buffer,
                              bufferSize,
                              &resultLength );

    if (NT_SUCCESS( status )) {

        pValuePartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION) buffer;
        ASSERT( pValuePartialInfo->Type == REG_DWORD );
        gFileSpyDebugLevel |= *((PULONG)&pValuePartialInfo->Data);
        
    }
    
    //
    // Read the attachment mode setting from the registry
    //

    RtlInitUnicodeString(&valueName, ATTACH_MODE);

    status = ZwQueryValueKey( driverRegKey,
                              &valueName,
                              KeyValuePartialInformation,
                              buffer,
                              bufferSize,
                              &resultLength );

    if (NT_SUCCESS( status )) {

        pValuePartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION) buffer;
        ASSERT( pValuePartialInfo->Type == REG_DWORD );
        gFileSpyAttachMode = *((PULONG)&pValuePartialInfo->Data);
    }
    
    goto SpyReadDriverParameters_Exit;

SpyReadDriverParameters_Exit:

    if (NULL != buffer) {

        ExFreePoolWithTag( buffer, FILESPY_POOL_TAG );
    }

    if (NULL != driverRegKey) {

        ZwClose(driverRegKey);
    }

    return;
}

#if WINVER >= 0x0501
VOID
SpyLoadDynamicFunctions (
    )
/*++

Routine Description:

    This routine tries to load the function pointers for the routines that
    are not supported on all versions of the OS.  These function pointers are
    then stored in the global structure gSpyDynamicFunctions.

    This support allows for one driver to be built that will run on all 
    versions of the OS Windows 2000 and greater.  Note that on Windows 2000, 
    the functionality may be limited.
    
Arguments:

    None.
    
Return Value:

    None.

--*/
{
    UNICODE_STRING functionName;

    RtlZeroMemory( &gSpyDynamicFunctions, sizeof( gSpyDynamicFunctions ) );

    //
    //  For each routine that we would want to use, lookup its address in the
    //  kernel or hal.  If it is not present, that field in our global
    //  gSpyDynamicFunctions structure will be set to NULL.
    //

    RtlInitUnicodeString( &functionName, L"FsRtlRegisterFileSystemFilterCallbacks" );
    gSpyDynamicFunctions.RegisterFileSystemFilterCallbacks = MmGetSystemRoutineAddress( &functionName );

    RtlInitUnicodeString( &functionName, L"IoAttachDeviceToDeviceStackSafe" );
    gSpyDynamicFunctions.AttachDeviceToDeviceStackSafe = MmGetSystemRoutineAddress( &functionName );
    
    RtlInitUnicodeString( &functionName, L"IoEnumerateDeviceObjectList" );
    gSpyDynamicFunctions.EnumerateDeviceObjectList = MmGetSystemRoutineAddress( &functionName );

    RtlInitUnicodeString( &functionName, L"IoGetLowerDeviceObject" );
    gSpyDynamicFunctions.GetLowerDeviceObject = MmGetSystemRoutineAddress( &functionName );

    RtlInitUnicodeString( &functionName, L"IoGetDeviceAttachmentBaseRef" );
    gSpyDynamicFunctions.GetDeviceAttachmentBaseRef = MmGetSystemRoutineAddress( &functionName );

    RtlInitUnicodeString( &functionName, L"IoGetDiskDeviceObject" );
    gSpyDynamicFunctions.GetDiskDeviceObject = MmGetSystemRoutineAddress( &functionName );

    RtlInitUnicodeString( &functionName, L"IoGetAttachedDeviceReference" );
    gSpyDynamicFunctions.GetAttachedDeviceReference = MmGetSystemRoutineAddress( &functionName );

    RtlInitUnicodeString( &functionName, L"RtlGetVersion" );
    gSpyDynamicFunctions.GetVersion = MmGetSystemRoutineAddress( &functionName );
}

VOID
SpyGetCurrentVersion (
    )
/*++

Routine Description:

    This routine reads the current OS version using the correct routine based
    on what routine is available.

Arguments:

    None.
    
Return Value:

    None.

--*/
{
    if (NULL != gSpyDynamicFunctions.GetVersion) {

        RTL_OSVERSIONINFOW versionInfo;
        NTSTATUS status;

        //
        //  VERSION NOTE: RtlGetVersion does a bit more than we need, but
        //  we are using it if it is available to show how to use it.  It
        //  is available on Windows XP and later.  RtlGetVersion and
        //  RtlVerifyVersionInfo (both documented in the IFS Kit docs) allow
        //  you to make correct choices when you need to change logic based
        //  on the current OS executing your code.
        //

        versionInfo.dwOSVersionInfoSize = sizeof( RTL_OSVERSIONINFOW );

        status = (gSpyDynamicFunctions.GetVersion)( &versionInfo );

        ASSERT( NT_SUCCESS( status ) );

        gSpyOsMajorVersion = versionInfo.dwMajorVersion;
        gSpyOsMinorVersion = versionInfo.dwMinorVersion;
        
    } else {

        PsGetVersion( &gSpyOsMajorVersion,
                      &gSpyOsMinorVersion,
                      NULL,
                      NULL );
    }
}

#endif
////////////////////////////////////////////////////////////////////////
//                                                                    //
//                  Memory allocation routines                        //
//                                                                    //
////////////////////////////////////////////////////////////////////////

PVOID
SpyAllocateBuffer (
    IN OUT PLONG Counter,
    IN LONG MaxCounterValue,
    OUT PULONG RecordType
    )
/*++

Routine Description:

    Allocates a new buffer from the gFreeBufferList if there is enough memory
    to do so and Counter does not exceed MaxCounterValue.  The RecordType
    is set to one of the record type constants based on the allocation state.

Arguments:

    Counter - (optional) the counter variable to test and increment if
        we can allocate
    MaxCounterValue - (ignored if Counter not given) the value which
        Counter should not exceed
    RecordType - (optional) set to one of the following:
        RECORD_TYPE_NORMAL  allocation succeeded
        RECORD_TYPE_OUT_OF_MEMORY allocation failed because the system was
                                  out of memory
        RECORD_TYPE_EXCEED_MEMORY_ALLOWANCE allocation failed because the
                                counter exceeded its maximum value.

Return Value:

    Pointer to the buffer allocate, or NULL if allocation failed (either
    because system is out of memory or we have exceeded the MaxCounterValue).

--*/
{
    PVOID newBuffer;
    ULONG newRecordType = RECORD_TYPE_NORMAL;

#ifdef MEMORY_DBG
    //
    //  When we are debugging the memory usage to make sure that we
    //  don't leak memory, we want to allocate the memory from pool
    //  so that we can use the Driver Verifier to help debug any
    //  memory problems.
    //

    newBuffer = ExAllocatePoolWithTag( NonPagedPool, 
                                       RECORD_SIZE, 
                                       FILESPY_LOGRECORD_TAG );
#else

    //
    //  When we are not debugging the memory usage, we use a look-aside
    //  list for better performance.
    //

    newBuffer = ExAllocateFromNPagedLookasideList( &gFreeBufferList );
#endif

    if (newBuffer) {

        if (Counter) {

            if (*Counter < MaxCounterValue) {

                InterlockedIncrement(Counter);

            } else {

				//
                // We've exceed our driver's memory limit so note that
                // and give back the record
				//

                SetFlag( newRecordType, 
                         (RECORD_TYPE_STATIC | RECORD_TYPE_EXCEED_MEMORY_ALLOWANCE) );

#ifdef MEMORY_DBG
                ExFreePoolWithTag( newBuffer, FILESPY_POOL_TAG );
#else
                ExFreeToNPagedLookasideList( &gFreeBufferList, newBuffer );
#endif

                newBuffer = NULL;
            }
        }

    }  else {

        SetFlag( newRecordType,
                 (RECORD_TYPE_STATIC | RECORD_TYPE_OUT_OF_MEMORY) );
    }

    if (RecordType) {

        *RecordType = newRecordType;
    }

    return newBuffer;
}

VOID
SpyFreeBuffer (
    IN PVOID Buffer,
    IN PLONG Counter
    )
/*++

Routine Description:

    Returns a Buffer to the gFreeBufferList.

Arguments:

    Buffer - the buffer to return to the gFreeBufferList

Return Value:

    None.

--*/
{

#ifdef MEMORY_DBG
    ExFreePoolWithTag( Buffer, FILESPY_POOL_TAG );
#else
    ExFreeToNPagedLookasideList( &gFreeBufferList, Buffer );
#endif

    //
    // Update the count
    //
    if (Counter) {

        InterlockedDecrement(Counter);
    }
}


////////////////////////////////////////////////////////////////////////
//                                                                    //
//                  Logging routines                                  //
//                                                                    //
////////////////////////////////////////////////////////////////////////

PRECORD_LIST
SpyNewRecord (
    IN ULONG AssignedSequenceNumber
    )
/*++

Routine Description:

    Allocates a new RECORD_LIST structure if there is enough memory to do so. A
    sequence number is updated for each request for a new record.

Arguments:

    AssignedSequenceNumber - 0 if you want this function to generate the
        next sequence number; if not 0, the new record is assigned the
        given sequence number.

Return Value:

    Pointer to the RECORD_LIST allocated, or NULL if no memory is available.

--*/
{
    PRECORD_LIST newRecord = NULL;
    ULONG currentSequenceNumber;
    KIRQL irql;
    ULONG initialRecordType;

    newRecord = (PRECORD_LIST) SpyAllocateBuffer( &gRecordsAllocated,
                                                  gMaxRecordsToAllocate,
                                                  &initialRecordType);

    KeAcquireSpinLock(&gLogSequenceLock, &irql);

    //
    // Assign a new sequence number if 0 was passed in, otherwise use the
    // number passed in
    //

    if (AssignedSequenceNumber == 0) {

        gLogSequenceNumber++;
        currentSequenceNumber = gLogSequenceNumber;

    } else {

        currentSequenceNumber = AssignedSequenceNumber;
    }


    if ((newRecord == NULL) &&
        !InterlockedCompareExchange( &gStaticBufferInUse, TRUE, FALSE)) {

        //
        // Toggle on our gStaticBufferInUse flag and use the static out of memory
        // buffer to record this log entry.  This special log record is used
        // to notify the user application that we are out of memory.  Log
        // request will be dropped until we can get more memory.
        //

        newRecord   = (PRECORD_LIST)gOutOfMemoryBuffer;
        newRecord->LogRecord.RecordType = initialRecordType;
        newRecord->LogRecord.Length = SIZE_OF_LOG_RECORD;
        newRecord->LogRecord.SequenceNumber = currentSequenceNumber;

    } else if (newRecord) {

		//
        // We were able to allocate a new record so initialize it
        // appropriately.
		//

        newRecord->LogRecord.RecordType = initialRecordType;
        newRecord->LogRecord.Length = SIZE_OF_LOG_RECORD;
        newRecord->LogRecord.SequenceNumber = currentSequenceNumber;
    }

    KeReleaseSpinLock(&gLogSequenceLock, irql);

    //
    //  Init record specific fields.
    //

    if (newRecord != NULL) {

        newRecord->NewContext = NULL;
        newRecord->WaitEvent = NULL;
        newRecord->Flags = 0;
    }

    return( newRecord );
}

VOID
SpyFreeRecord (
    IN PRECORD_LIST Record
    )
/*++

Routine Description:

    Frees a RECORD_LIST, which returns the memory to the gFreeBufferList look-aside
    list and updates the gRecordsAllocated count.

Arguments:

    Record - the record to free

Return Value:

    None.

--*/
{
    //
    //  If there is a context record defined, release it now
    //

#if USE_STREAM_CONTEXTS
    if (NULL != Record->NewContext) {

        SpyReleaseContext( Record->NewContext );
    }
#endif

    if (FlagOn( Record->LogRecord.RecordType, RECORD_TYPE_STATIC )) {

		//
        // This is our static record, so reset our gStaticBufferInUse
        // flag.
		//

        InterlockedExchange( &gStaticBufferInUse, FALSE );

    } else {

		//
        // This isn't our static memory buffer, so free the dynamically
        // allocated memory.
		//

        SpyFreeBuffer( Record, &gRecordsAllocated );
    }
}


PRECORD_LIST
SpyLogFastIoStart (
    IN FASTIO_TYPE FastIoType,
    IN PDEVICE_OBJECT DeviceObject,
    IN PFILE_OBJECT FileObject OPTIONAL,
    IN PLARGE_INTEGER FileOffset OPTIONAL,
    IN ULONG Length OPTIONAL,
    IN BOOLEAN Wait	OPTIONAL
    )
/*++

Routine Description:

    Creates the log record if possible and records the necessary Fast I/O
    information at the beginning of the fast I/O operation in RecordList
    according to LoggingFlags.

    The optional arguments are not recorded for all Fast I/O types.  If
    the argument is not needed for a given Fast I/O type, the parameter
    was ignored.

Arguments:

    FastIoType - The type of fast I/O we are logging (REQUIRED)
    DeviceObject - The device object for our filter. (REQUIRED)
    FileObject - Pointer to the file object this operation is on (OPTIONAL)
    FileOffset - Pointer to the file offset for this operation (OPTIONAL)
    Length - Length of the data for this operation (OPTIONAL)
    Wait - Whether or not this operation can wait for a result (OPTIONAL)

Return Value:

    The RECORD_LIST structure created with the appropriate information
    filled in.  If a RECORD_LIST structure couldn't be allocated, NULL
    is returned.

--*/
{
    PRECORD_LIST    pRecordList;
    PRECORD_FASTIO  pRecordFastIo;
    PFILESPY_DEVICE_EXTENSION devExt;

    //
    // Try to get a new record
    //

    pRecordList = SpyNewRecord(0);

    //
    // If we didn't get a RECORD_LIST, exit and return NULL
    //

    if (pRecordList == NULL) {

        return NULL;
    }

    //
    // We got a RECORD_LIST, so now fill in the appropriate information
    //

    pRecordFastIo = &pRecordList->LogRecord.Record.RecordFastIo;

    //
    // Perform the necessary book keeping for the RECORD_LIST
    //

    SetFlag( pRecordList->LogRecord.RecordType, RECORD_TYPE_FASTIO );

    //
    // Set the RECORD_FASTIO fields that are set for all Fast I/O types
    //

    pRecordFastIo->Type = FastIoType;
    KeQuerySystemTime(&pRecordFastIo->StartTime);

    //
    // Get process and thread information
    //

    pRecordFastIo->ProcessId = (ULONG_PTR) PsGetCurrentProcessId();
    pRecordFastIo->ThreadId = (ULONG_PTR) PsGetCurrentThreadId();

    //
    // Record the information that is appropriate based on the
    // Fast I/O type
    //

    pRecordFastIo->FileObject = (FILE_ID)FileObject;
    pRecordFastIo->DeviceObject = (DEVICE_ID)DeviceObject;
    pRecordFastIo->FileOffset.QuadPart = ((FileOffset != NULL) ? FileOffset->QuadPart : 0);
    pRecordFastIo->Length = Length;
    pRecordFastIo->Wait = Wait;

    devExt = DeviceObject->DeviceExtension;

    if (FastIoType == CHECK_IF_POSSIBLE) {

        //
        //  On NTFS, locks are sometimes held but top-level irp is not set, 
        //  therefore it is not safe to query the base file system for the
        //  file name at this time.  If we've got it in the cache, we'll
        //  use it.  Otherwise, we will not return a name.
        //
        
        SpySetName(pRecordList, DeviceObject, FileObject, NLFL_ONLY_CHECK_CACHE, NULL);
        
    } else {

        SpySetName(pRecordList, DeviceObject, FileObject, 0, NULL);
    }

    return pRecordList;
}

VOID
SpyLogFastIoComplete (
    IN PIO_STATUS_BLOCK ReturnStatus,
    IN PRECORD_LIST RecordList
    )
/*++

Routine Description:

    Records the necessary Fast I/O information in RecordList according to
    LoggingFlags.

    The optional arguments are not recorded for all Fast I/O types.  If
    the argument is not needed for a given Fast I/O type, the parameter
    was ignored.

Arguments:
    ReturnStatus - The return value of the operation (OPTIONAL)
    RecordList - The PRECORD_LIST in which the Fast I/O information is stored.

Return Value:

    None.

--*/
{
    PRECORD_FASTIO pRecordFastIo;

    ASSERT(RecordList);

    pRecordFastIo = &RecordList->LogRecord.Record.RecordFastIo;

    //
    // Set the RECORD_FASTIO fields that are set for all Fast I/O types
    //

    KeQuerySystemTime(&pRecordFastIo->CompletionTime);

    if (ReturnStatus != NULL) {

        pRecordFastIo->ReturnStatus = ReturnStatus->Status;

    } else {

        pRecordFastIo->ReturnStatus = 0;
    }

    SpyLog(RecordList);
}

#if WINVER >= 0x0501 /* See comment in DriverEntry */

VOID
SpyLogPreFsFilterOperation (
    IN PFS_FILTER_CALLBACK_DATA Data,
    OUT PRECORD_LIST RecordList
    )
{
    NAME_LOOKUP_FLAGS lookupFlags = 0;
    
    PRECORD_FS_FILTER_OPERATION pRecordFsFilterOp;

    pRecordFsFilterOp = &RecordList->LogRecord.Record.RecordFsFilterOp;
    
    //
    // Record the information we use for an originating Irp.  We first
    // need to initialize some of the RECORD_LIST and RECORD_IRP fields.
    // Then get the interesting information from the Irp.
    //

    SetFlag( RecordList->LogRecord.RecordType, RECORD_TYPE_FS_FILTER_OP );

    pRecordFsFilterOp->FsFilterOperation = Data->Operation;
    pRecordFsFilterOp->FileObject = (FILE_ID) Data->FileObject;
    pRecordFsFilterOp->DeviceObject = (FILE_ID) Data->DeviceObject;
    pRecordFsFilterOp->ProcessId = (FILE_ID)PsGetCurrentProcessId();
    pRecordFsFilterOp->ThreadId = (FILE_ID)PsGetCurrentThreadId();
    
    KeQuerySystemTime(&pRecordFsFilterOp->OriginatingTime);

    //
    //  Do not query for the name on any of the release operations
    //  because a file system resource is currently being held and
    //  we may deadlock.
    //

    switch (Data->Operation) {

        case FS_FILTER_RELEASE_FOR_CC_FLUSH:
        case FS_FILTER_RELEASE_FOR_SECTION_SYNCHRONIZATION:
        case FS_FILTER_RELEASE_FOR_MOD_WRITE:

            SPY_LOG_PRINT( SPYDEBUG_TRACE_DETAILED_CONTEXT_OPS, 
                           ("FileSpy!SpyLogPreFsFilterOp:   RelOper\n") );

            SetFlag( lookupFlags, NLFL_ONLY_CHECK_CACHE );
            break;
    }

    //
    //  Only set the volumeName if the next device is a file system
    //  since we only want to prepend the volumeName if we are on
    //  top of a local file system.
    //

    SpySetName( RecordList, Data->DeviceObject, Data->FileObject, lookupFlags, NULL);
}

VOID
SpyLogPostFsFilterOperation (
    IN NTSTATUS OperationStatus,
    OUT PRECORD_LIST RecordList
    )
{
    PRECORD_FS_FILTER_OPERATION pRecordFsFilterOp;

    pRecordFsFilterOp = &RecordList->LogRecord.Record.RecordFsFilterOp;
    
    //
    // Record the information we see in the post operation.
    //

    pRecordFsFilterOp->ReturnStatus = OperationStatus;
    KeQuerySystemTime(&pRecordFsFilterOp->CompletionTime);
}

#endif

NTSTATUS
SpyAttachDeviceToDeviceStack (
    IN PDEVICE_OBJECT SourceDevice,
    IN PDEVICE_OBJECT TargetDevice,
    IN OUT PDEVICE_OBJECT *AttachedToDeviceObject
    )
/*++

Routine Description:

    This routine attaches the SourceDevice to the TargetDevice's stack and
    returns the device object SourceDevice was directly attached to in 
    AttachedToDeviceObject.  Note that the SourceDevice does not necessarily
    get attached directly to TargetDevice.  The SourceDevice will get attached
    to the top of the stack of which TargetDevice is a member.

    VERSION NOTE:

    In Windows XP, a new API was introduced to close a rare timing window that 
    can cause IOs to start being sent to a device before its 
    AttachedToDeviceObject is set in its device extension.  This is possible
    if a filter is attaching to a device stack while the system is actively
    processing IOs.  The new API closes this timing window by setting the
    device extension field that holds the AttachedToDeviceObject while holding
    the IO Manager's lock that protects the device stack.

    A sufficient work around for earlier versions of the OS is to set the
    AttachedToDeviceObject to the device object that the SourceDevice is most
    likely to attach to.  While it is possible that another filter will attach
    in between the SourceDevice and TargetDevice, this will prevent the
    system from bug checking if the SourceDevice receives IOs before the 
    AttachedToDeviceObject is correctly set.

    For a driver built in the Windows 2000 build environment, we will always 
    use the work-around code to attach.  For a driver that is built in the
    Windows XP or later build environments (therefore you are building a 
    multiversion driver), we will determine which method of attachment to use 
    based on which APIs are available.
    

Arguments:

    SourceDevice - The device object to be attached to the stack.

    TargetDevice - The device that we currently think is the top of the stack
        to which SourceDevice should be attached.

    AttachedToDeviceObject - This is set to the device object to which 
        SourceDevice is attached if the attach is successful.
        
Return Value:

    Return STATUS_SUCCESS if the device is successfully attached.  If 
    TargetDevice represents a stack to which devices can no longer be attached,
    STATUS_NO_SUCH_DEVICE is returned.

--*/
{

    PAGED_CODE();

#if WINVER >= 0x0501
    if (IS_WINDOWSXP_OR_LATER()) {

        ASSERT( NULL != gSpyDynamicFunctions.AttachDeviceToDeviceStackSafe );
        
        return (gSpyDynamicFunctions.AttachDeviceToDeviceStackSafe)( SourceDevice,
                                                                     TargetDevice,
                                                                     AttachedToDeviceObject );

    } else {
#endif

        *AttachedToDeviceObject = TargetDevice;
        *AttachedToDeviceObject = IoAttachDeviceToDeviceStack( SourceDevice,
                                                               TargetDevice );

        if (*AttachedToDeviceObject == NULL) {

            return STATUS_NO_SUCH_DEVICE;
        }

        return STATUS_SUCCESS;

#if WINVER >= 0x0501
    }
#endif

}

NTSTATUS
SpyLog (
    IN PRECORD_LIST NewRecord
    )
/*++

Routine Description:

    This routine appends the completed log record to the gOutputBufferList.

Arguments:

    NewRecord - The record to append to the gOutputBufferList

Return Value:

    The function returns STATUS_SUCCESS.

--*/
{
    KIRQL controlDeviceIrql;
    KIRQL outputBufferIrql;

    KeAcquireSpinLock( &gControlDeviceStateLock, &controlDeviceIrql );

    if (gControlDeviceState == OPENED) {

        //
        // The device is still open so add this record onto the list
        //

        KeAcquireSpinLock(&gOutputBufferLock, &outputBufferIrql);
        InsertTailList(&gOutputBufferList, &NewRecord->List);
        KeReleaseSpinLock(&gOutputBufferLock, outputBufferIrql);

    } else {

        //
        // We can no longer log this record, so free the record
        //

        SpyFreeRecord( NewRecord );

    }

    KeReleaseSpinLock( &gControlDeviceStateLock, controlDeviceIrql );

    return STATUS_SUCCESS;
}

////////////////////////////////////////////////////////////////////////
//                                                                    //
//                    FileName cache routines                         //
//                                                                    //
////////////////////////////////////////////////////////////////////////

BOOLEAN
SpyGetFullPathName (
    IN PFILE_OBJECT FileObject,
    IN OUT PUNICODE_STRING FileName,
    IN PFILESPY_DEVICE_EXTENSION devExt,
    IN NAME_LOOKUP_FLAGS LookupFlags
    )
/*++

Routine Description:

    This routine retrieves the full pathname of the FileObject.  Note that
    the buffers containing pathname components may be stored in paged pool,
    therefore if we are at DISPATCH_LEVEL we cannot look up the name.

    The file is looked up one of the following ways based on the LookupFlags:
    1.  FlagOn( FileObject->Flags, FO_VOLUME_OPEN ) or (FileObject->FileName.Length == 0).
        This is a volume open, so just use DeviceName from the devExt 
        for the FileName, if it exists.
    2.  NAMELOOKUPFL_IN_CREATE and NAMELOOKUPFL_OPEN_BY_ID are set.
        This is an open by file id, so format the file id into the FileName
        string if there is enough room.
    3.  NAMELOOKUPFL_IN_CREATE set and FileObject->RelatedFileObject != NULL.
        This is a relative open, therefore the fullpath file name must
        be built up from the name of the FileObject->RelatedFileObject
        and FileObject->FileName.
    4.  NAMELOOKUPFL_IN_CREATE and FileObject->RelatedFileObject == NULL.
        This is an absolute open, therefore the fullpath file name is
        found in FileObject->FileName.
    5.  No LookupFlags set.
        This is a lookup sometime after CREATE.  FileObject->FileName is 
        no longer guaranteed to be valid, so use ObQueryNameString
        to get the fullpath name of the FileObject.
    
Arguments:

    FileObject - Pointer to the FileObject to the get name of.

    FileName - Unicode string that will be filled in with the filename,  It 
        is assumed that the caller allocates and frees the memory used by 
        the string.  The buffer and MaximumLength for this string should be 
        set.  If there is room in the buffer, the string will be NULL 
        terminated.

    devExt - Contains the device name and next device object
        which are needed to build the full path name.

    LookupFlags - The flags to say whether to get the name from the file
        object or to get the file id.

Return Value:

    Returns TRUE if the returned name should be saved in the cache,
    returns FALSE if the returned name should NOT be saved in the cache.
    In all cases some sort of valid name is always returned.

--*/
{
    NTSTATUS status;
    ULONG i;
    BOOLEAN retValue = TRUE;
    UCHAR buffer[sizeof(FILE_NAME_INFORMATION) + MAX_NAME_SPACE];

    //
    //  Copy over the name the user gave for this device.  These names
    //  should be meaningful to the user.  Note that we do not do this for
    //  NETWORK file system because internally they already show the
    //  connection name.  If this is a direct device open of the network
    //  file system device, we will copy over the device name to be
    //  returned to the user.
    //
    
    if (FILE_DEVICE_NETWORK_FILE_SYSTEM != devExt->ThisDeviceObject->DeviceType) {

        RtlCopyUnicodeString( FileName, &devExt->UserNames );
        
    } else if (FlagOn( FileObject->Flags, FO_DIRECT_DEVICE_OPEN )) {

        ASSERT( devExt->ThisDeviceObject->DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM );
        RtlCopyUnicodeString( FileName, &devExt->DeviceName );

        //
        //  We are now done since there will be no more to the name in this
        //  case, so return TRUE.
        //
        
        return TRUE;
    }

    //
    //  See if we can request the name
    //

    if (FlagOn( LookupFlags, NLFL_ONLY_CHECK_CACHE )) {

        RtlAppendUnicodeToString( FileName, L"[-=Not In Cache=-]" );
        return FALSE;
    }

    //
    //  Can not get the name at DPC level
    //

    if (KeGetCurrentIrql() > APC_LEVEL) {

        RtlAppendUnicodeToString( FileName, L"[-=At DPC Level=-]" );
        return FALSE;
    }

    //
    //  If there is a ToplevelIrp then this is a nested operation and
    //  there might be other locks held.  Can not get name without the
    //  potential of deadlocking.
    //

    if (IoGetTopLevelIrp() != NULL) {

        RtlAppendUnicodeToString( FileName, L"[-=Nested Operation=-]" );
        return FALSE;
    }

    //
    //  CASE 1:  This FileObject refers to a Volume open.  Either the
    //           flag is set or no filename is specified.
    //

    if (FlagOn( FileObject->Flags, FO_VOLUME_OPEN ) ||
        (FlagOn( LookupFlags, NLFL_IN_CREATE ) &&
         (FileObject->FileName.Length == 0) && 
         (FileObject->RelatedFileObject == NULL))) {

        //
        //  We've already copied the VolumeName so just return.
        //

    }

    //
    //  CASE 2:  We are opening the file by ID.
    //

    else if (FlagOn( LookupFlags, NLFL_IN_CREATE ) &&
             FlagOn( LookupFlags, NLFL_OPEN_BY_ID )) {

#       define OBJECT_ID_KEY_LENGTH 16
        UNICODE_STRING fileIdName;

        RtlInitEmptyUnicodeString( &fileIdName,
                                   (PWSTR)buffer,
                                   sizeof(buffer) );

        if (FileObject->FileName.Length == sizeof(LONGLONG)) {

			//
            //  Opening by FILE ID, generate a name
			//
			
            swprintf( fileIdName.Buffer, 
                      L"<%016I64x>", 
                      *((PLONGLONG)FileObject->FileName.Buffer) );

        } else if ((FileObject->FileName.Length == OBJECT_ID_KEY_LENGTH) ||
                   (FileObject->FileName.Length == OBJECT_ID_KEY_LENGTH + 
                                                                sizeof(WCHAR)))
        {
            PUCHAR idBuffer;

            //
            //  Opening by Object ID, generate a name
            //

            idBuffer = (PUCHAR)&FileObject->FileName.Buffer[0];

            if (FileObject->FileName.Length != OBJECT_ID_KEY_LENGTH) {

                //
                //  Skip win32 backslash at start of buffer
                //
                idBuffer = (PUCHAR)&FileObject->FileName.Buffer[1];
            }

            swprintf( fileIdName.Buffer,
					  L"<%08x-%04hx-%04hx-%04hx-%04hx%08x>",
                      *(PULONG)&idBuffer[0],
                      *(PUSHORT)&idBuffer[0+4],
                      *(PUSHORT)&idBuffer[0+4+2],
                      *(PUSHORT)&idBuffer[0+4+2+2],
                      *(PUSHORT)&idBuffer[0+4+2+2+2],
                      *(PULONG)&idBuffer[0+4+2+2+2+2]);

        } else {

			//
            //  Unknown ID format
			//

            swprintf( fileIdName.Buffer,
                      L"[-=Unknown ID (Len=%u)=-]",
                      FileObject->FileName.Length);
        }

        fileIdName.Length = wcslen( fileIdName.Buffer ) * sizeof( WCHAR );

        //
        //  Append the fileIdName to FileName.
        //

        RtlAppendUnicodeStringToString( FileName, &fileIdName );

        //
        //  Don't cache the ID name
        //

        retValue = FALSE;
    } 

    //
    //  CASE 3: We are opening a file that has a RelatedFileObject.
    //
    
    else if (FlagOn( LookupFlags, NLFL_IN_CREATE ) &&
             (NULL != FileObject->RelatedFileObject)) {

        //
        //  Must be a relative open.  Use ObQueryNameString to get
        //  the name of the related FileObject.  Then we will append this
        //  fileObject's name.
        //
        //  Note: 
        //  The name in FileObject and FileObject->RelatedFileObject are accessible.  Names further up
        //  the related file object chain (ie FileObject->RelatedFileObject->RelatedFileObject)
        //  may not be accessible.  This is the reason we use ObQueryNameString
        //  to get the name for the RelatedFileObject.
        //

        PFILE_NAME_INFORMATION relativeNameInfo = (PFILE_NAME_INFORMATION)buffer;
        ULONG returnLength;

        status = SpyQueryFileSystemForFileName( FileObject->RelatedFileObject,
                                                devExt->AttachedToDeviceObject,
                                                sizeof( buffer ),
                                                relativeNameInfo,
                                                &returnLength );

        if (NT_SUCCESS( status ) &&
            ((FileName->Length + relativeNameInfo->FileNameLength + FileObject->FileName.Length + sizeof( L'\\' ))
             <= FileName->MaximumLength)) {

            //
            //  We were able to get the relative fileobject's name and we have
            //  enough room in the FileName buffer, so build up the file name
            //  in the following format:
            //      [volumeName]\[relativeFileObjectName]\[FileObjectName]
            //  The VolumeName is already in FileName if we've got one.
            //

            RtlCopyMemory( &FileName->Buffer[FileName->Length/sizeof(WCHAR)],
                           relativeNameInfo->FileName,
                           relativeNameInfo->FileNameLength );

            FileName->Length += (USHORT)relativeNameInfo->FileNameLength;

        } else if ((FileName->Length + FileObject->FileName.Length + sizeof(L"...\\")) <=
                   FileName->MaximumLength ) {

            //
            //  Either the query for the relative fileObject name was unsuccessful,
            //  or we don't have enough room for the relativeFileObject name, but we
            //  do have enough room for "...\[fileObjectName]" in FileName.
            //

            status = RtlAppendUnicodeToString( FileName, L"...\\" );
            ASSERT( status == STATUS_SUCCESS );
        }

        //
        //  If there is not a slash and the end of the related file object
        //  string and there is not a slash at the front of the file object
        //  string, then add one.
        //

        if (((FileName->Length < sizeof(WCHAR) ||
             (FileName->Buffer[(FileName->Length/sizeof(WCHAR))-1] != L'\\'))) &&
            ((FileObject->FileName.Length < sizeof(WCHAR)) ||
             (FileObject->FileName.Buffer[0] != L'\\')))
        {

            RtlAppendUnicodeToString( FileName, L"\\" );
        }

        //
        //  At this time, copy over the FileObject->FileName to the FileName
        //  unicode string.
        //

        RtlAppendUnicodeStringToString( FileName, &FileObject->FileName );
    }
    
    //
    //  CASE 4: We have a open on a file with an absolute path.
    //
    
    else if (FlagOn( LookupFlags, NLFL_IN_CREATE ) &&
             (FileObject->RelatedFileObject == NULL) ) {

        // 
        //  We have an absolute path, so try to copy that into FileName.
        //

        RtlAppendUnicodeStringToString( FileName, &FileObject->FileName );
    }

    //
    //  CASE 5: We are retrieving the file name sometime after the
    //  CREATE operation.
    //

    else if (!FlagOn( LookupFlags, NLFL_IN_CREATE )) {

        PFILE_NAME_INFORMATION nameInfo = (PFILE_NAME_INFORMATION)buffer;
        ULONG returnLength;

        status = SpyQueryFileSystemForFileName( FileObject,
                                                devExt->AttachedToDeviceObject,
                                                sizeof( buffer ),
                                                nameInfo,
                                                &returnLength );

        if (NT_SUCCESS( status )) {

            if ((FileName->Length + nameInfo->FileNameLength) <= FileName->MaximumLength) {

                //
                //  We've got enough room for the file name, so copy it into
                //  FileName.
                //

                RtlCopyMemory( &FileName->Buffer[FileName->Length/sizeof(WCHAR)],
                               nameInfo->FileName,
                               nameInfo->FileNameLength );

                FileName->Length += (USHORT)nameInfo->FileNameLength;
                               
            } else {

                //
                //  We don't have enough room for the file name, so copy our
                //  EXCEED_NAME_BUFFER error message.
                //

                RtlAppendUnicodeToString( FileName, 
                                          L"[-=Name To Large=-]" );
            }
            
        } else {

            //
            //  Got an error trying to get the file name from the base file system,
            //  so put that error message into FileName.
            //

            swprintf((PWCHAR)buffer,L"[-=Error 0x%x Getting Name=-]",status );

            RtlAppendUnicodeToString( FileName, (PWCHAR)buffer );

            //
            //  Don't cache an error-generated name
            //

            retValue = FALSE;
        }
    }

    //
    //  When we get here we have a valid name.
    //  Sometimes when we query a name it has a trailing slash, other times
    //  it doesn't.  To make sure the contexts are correct we are going to
    //  remove a trailing slash if there is not a ":" just before it.
    //

    if ((FileName->Length >= (2*sizeof(WCHAR))) &&
        (FileName->Buffer[(FileName->Length/sizeof(WCHAR))-1] == L'\\') &&
        (FileName->Buffer[(FileName->Length/sizeof(WCHAR))-2] != L':'))
    {

        FileName->Length -= sizeof(WCHAR);
    }

    //
    //  See if we are actually opening the target directory.  If so then
    //  remove the trailing name and slash.  Note that we won't remove
    //  the initial slash (just after the colon).
    //

    if (FlagOn( LookupFlags, NLFL_OPEN_TARGET_DIR ) &&
        (FileName->Length > 0))
    {
        i = (FileName->Length / sizeof(WCHAR)) - 1;

        //
        //  See if the path ends in a backslash, if so skip over it
        //  (since the file system did).
        //

        if ((i > 0) &&
            (FileName->Buffer[i] == L'\\') &&
            (FileName->Buffer[i-1] != L':')) {

            i--;
        }

        //
        //  Scan backwards over the last component
        //

        for ( ;
              i > 0;
              i-- )
        {

            if (FileName->Buffer[i] == L'\\') {

                if ((i > 0) && (FileName->Buffer[i-1] == L':')) {

                    i++;
                }

                FileName->Length = (USHORT)(i * sizeof(WCHAR));
                break;
            }
        }
    }

    return retValue;
}


NTSTATUS
SpyQueryCompletion (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PKEVENT SynchronizingEvent
    )
/*++

Routine Description:

    This routine does the cleanup necessary once the query request completed
    by the file system.
    
Arguments:

    DeviceObject - This will be NULL since we originated this
        Irp.

    Irp - The io request structure containing the information
        about the current state of our file name query.

    SynchronizingEvent - The event to signal to notify the 
        originator of this request that the operation is
        complete.

Return Value:

    Returns STATUS_MORE_PROCESSING_REQUIRED so that IO Manager
    will not try to free the Irp again.

--*/
{

    UNREFERENCED_PARAMETER( DeviceObject );
    
    //
    //  Make sure that the Irp status is copied over to the user's
    //  IO_STATUS_BLOCK so that the originator of this irp will know
    //  the final status of this operation.
    //

    ASSERT( NULL != Irp->UserIosb );
    *Irp->UserIosb = Irp->IoStatus;

    //
    //  Signal SynchronizingEvent so that the originator of this
    //  Irp know that the operation is completed.
    //

    KeSetEvent( SynchronizingEvent, IO_NO_INCREMENT, FALSE );

    //
    //  We are now done, so clean up the irp that we allocated.
    //

    IoFreeIrp( Irp );

    //
    //  If we return STATUS_SUCCESS here, the IO Manager will
    //  perform the cleanup work that it thinks needs to be done
    //  for this IO operation.  This cleanup work includes:
    //  * Copying data from the system buffer to the user's buffer 
    //    if this was a buffered IO operation.
    //  * Freeing any MDLs that are in the Irp.
    //  * Copying the Irp->IoStatus to Irp->UserIosb so that the
    //    originator of this irp can see the final status of the
    //    operation.
    //  * If this was an asynchronous request or this was a 
    //    synchronous request that got pending somewhere along the
    //    way, the IO Manager will signal the Irp->UserEvent, if one 
    //    exists, otherwise it will signal the FileObject->Event.
    //    (This can have REALLY bad implications if the irp originator
    //     did not an Irp->UserEvent and the irp originator is not
    //     waiting on the FileObject->Event.  It would not be that
    //     farfetched to believe that someone else in the system is
    //     waiting on FileObject->Event and who knows who will be
    //     awoken as a result of the IO Manager signaling this event.
    //
    //  Since some of these operations require the originating thread's
    //  context (e.g., the IO Manager need the UserBuffer address to 
    //  be valid when copy is done), the IO Manager queues this work
    //  to an APC on the Irp's originating thread.
    //
    //  Since FileSpy allocated and initialized this irp, we know
    //  what cleanup work needs to be done.  We can do this cleanup
    //  work more efficiently than the IO Manager since we are handling
    //  a very specific case.  Therefore, it is better for us to
    //  perform the cleanup work here then free the irp than passing
    //  control back to the IO Manager to do this work.
    //
    //  By returning STATUS_MORE_PROCESS_REQUIRED, we tell the IO Manager 
    //  to stop processing this irp until it is told to restart processing
    //  with a call to IoCompleteRequest.  Since the IO Manager has
    //  already performed all the work we want it to do on this
    //  irp, we do the cleanup work, return STATUS_MORE_PROCESSING_REQUIRED,
    //  and ask the IO Manager to resume processing by calling 
    //  IoCompleteRequest.
    //

    return STATUS_MORE_PROCESSING_REQUIRED;
}


NTSTATUS
SpyQueryFileSystemForFileName (
    IN PFILE_OBJECT FileObject,
    IN PDEVICE_OBJECT NextDeviceObject,
    IN ULONG FileNameInfoLength,
    OUT PFILE_NAME_INFORMATION FileNameInfo,
    OUT PULONG ReturnedLength
    )
/*++

Routine Description:

    This routine rolls an irp to query the name of the
    FileObject parameter from the base file system.

    Note:  ObQueryNameString CANNOT be used here because it
      would cause recursive lookup of the file name for FileObject.
      
Arguments:

    FileObject - the file object for which we want the name.
    NextDeviceObject - the device object for the next driver in the
        stack.  This is where we want to start our request
        for the name of FileObject.
    FileNameInfoLength - the length in bytes of FileNameInfo
        parameter.
    FileNameInfo - the buffer that will be receive the name
        information.  This must be memory that safe to write
        to from kernel space.
    ReturnedLength - the number of bytes written to FileNameInfo.
    
Return Value:

    Returns the status of the operation.
    
--*/
{
    PIRP irp;
    PIO_STACK_LOCATION irpSp;
    KEVENT event;
    IO_STATUS_BLOCK ioStatus;
    NTSTATUS status;

    PAGED_CODE();

    irp = IoAllocateIrp( NextDeviceObject->StackSize, FALSE );
    
    if (irp == NULL) {

        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    //  Set our current thread as the thread for this
    //  irp so that the IO Manager always knows which
    //  thread to return to if it needs to get back into
    //  the context of the thread that originated this
    //  irp.
    //
    
    irp->Tail.Overlay.Thread = PsGetCurrentThread();

    //
    //  Set that this irp originated from the kernel so that
    //  the IO Manager knows that the buffers do not
    //  need to be probed.
    //
    
    irp->RequestorMode = KernelMode;

    //
    //  Initialize the UserIosb and UserEvent in the 
    //

    ioStatus.Status = STATUS_SUCCESS;
    ioStatus.Information = 0;

    irp->UserIosb = &ioStatus;
    irp->UserEvent = NULL;        //already zeroed

    //
    //  Set the IRP_SYNCHRONOUS_API to denote that this
    //  is a synchronous IO request.
    //

    irp->Flags = IRP_SYNCHRONOUS_API;

    irpSp = IoGetNextIrpStackLocation( irp );

    irpSp->MajorFunction = IRP_MJ_QUERY_INFORMATION;
    irpSp->FileObject = FileObject;

    //
    //  Setup the parameters for IRP_MJ_QUERY_INFORMATION.
    //  The buffer we want to be filled in should be placed in
    //  the system buffer.
    //

    irp->AssociatedIrp.SystemBuffer = FileNameInfo;

    irpSp->Parameters.QueryFile.Length = FileNameInfoLength;
    irpSp->Parameters.QueryFile.FileInformationClass = FileNameInformation;

    //
    //  Set up the completion routine so that we know when our
    //  request for the file name is completed.  At that time,
    //  we can free the irp.
    //
    
    KeInitializeEvent( &event, NotificationEvent, FALSE );

    IoSetCompletionRoutine( irp, 
                            SpyQueryCompletion, 
                            &event, 
                            TRUE, 
                            TRUE, 
                            TRUE );

    status = IoCallDriver( NextDeviceObject, irp );

    SPY_LOG_PRINT( SPYDEBUG_TRACE_NAME_REQUESTS,
                   ("FileSpy!SpyQueryFileSystemForFileName: Issued name request -- IoCallDriver status: 0x%08x\n",
                    status) );

    if (STATUS_PENDING == status) {

        (VOID) KeWaitForSingleObject( &event, 
                                      Executive, 
                                      KernelMode,
                                      FALSE,
                                      NULL );
    }

    ASSERT(KeReadStateEvent(&event) || !NT_SUCCESS(ioStatus.Status));

    SPY_LOG_PRINT( SPYDEBUG_TRACE_NAME_REQUESTS,
                   ("FileSpy!SpyQueryFileSystemForFileName: Finished waiting for name request to complete...\n") );

    *ReturnedLength = (ULONG) ioStatus.Information;
    return ioStatus.Status;
}


NTSTATUS
SpyQueryInformationFile (
	IN PDEVICE_OBJECT NextDeviceObject,
	IN PFILE_OBJECT FileObject,
	OUT PVOID FileInformation,
	IN ULONG Length,
	IN FILE_INFORMATION_CLASS FileInformationClass,
	OUT PULONG LengthReturned OPTIONAL
	)

/*++

Routine Description:

    This routine returns the requested information about a specified file.
    The information returned is determined by the FileInformationClass that
    is specified, and it is placed into the caller's FileInformation buffer.

Arguments:

    NextDeviceObject - Supplies the device object where this IO should start
        in the device stack.

    FileObject - Supplies the file object about which the requested
        information should be returned.

    FileInformation - Supplies a buffer to receive the requested information
        returned about the file.  This must be a buffer allocated from kernel
        space.

    Length - Supplies the length, in bytes, of the FileInformation buffer.

    FileInformationClass - Specifies the type of information which should be
        returned about the file.

    LengthReturned - the number of bytes returned if the operation was 
        successful.

Return Value:

    The status returned is the final completion status of the operation.

--*/

{
    PIRP irp = NULL;
    PIO_STACK_LOCATION irpSp = NULL;
    IO_STATUS_BLOCK ioStatusBlock;
    KEVENT event;
    NTSTATUS status;

    PAGED_CODE();
    
    //
    //  In DBG builds, make sure that we have valid parameters before we do 
    //  any work here.
    //

    ASSERT( NULL != NextDeviceObject );
    ASSERT( NULL != FileObject );
    ASSERT( NULL != FileInformation );
    
    //
    //  The parameters look ok, so setup the Irp.
    //

    KeInitializeEvent( &event, NotificationEvent, FALSE );
    ioStatusBlock.Status = STATUS_SUCCESS;
    ioStatusBlock.Information = 0;

    irp = IoAllocateIrp( NextDeviceObject->StackSize, FALSE );
    
    if (irp == NULL) {

        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    //  Set our current thread as the thread for this
    //  irp so that the IO Manager always knows which
    //  thread to return to if it needs to get back into
    //  the context of the thread that originated this
    //  irp.
    //
    
    irp->Tail.Overlay.Thread = PsGetCurrentThread();

    //
    //  Set that this irp originated from the kernel so that
    //  the IO Manager knows that the buffers do not
    //  need to be probed.
    //
    
    irp->RequestorMode = KernelMode;

    //
    //  Initialize the UserIosb and UserEvent in the 
    irp->UserIosb = &ioStatusBlock;
    irp->UserEvent = NULL;

    //
    //  Set the IRP_SYNCHRONOUS_API to denote that this
    //  is a synchronous IO request.
    //

    irp->Flags = IRP_SYNCHRONOUS_API;

    irpSp = IoGetNextIrpStackLocation( irp );

    irpSp->MajorFunction = IRP_MJ_QUERY_INFORMATION;
    irpSp->FileObject = FileObject;

    //
    //  Setup the parameters for IRP_MJ_QUERY_INFORMATION.  These
    //  were supplied by the caller of this routine.
    //  The buffer we want to be filled in should be placed in
    //  the system buffer.
    //

    irp->AssociatedIrp.SystemBuffer = FileInformation;

    irpSp->Parameters.QueryFile.Length = Length;
    irpSp->Parameters.QueryFile.FileInformationClass = FileInformationClass;

    //
    //  Set up the completion routine so that we know when our
    //  request for the file name is completed.  At that time,
    //  we can free the irp.
    //
    
    IoSetCompletionRoutine( irp, 
                            SpyQueryCompletion, 
                            &event, 
                            TRUE, 
                            TRUE, 
                            TRUE );

    status = IoCallDriver( NextDeviceObject, irp );

    if (STATUS_PENDING == status) {

        KeWaitForSingleObject( &event, 
                               Executive, 
                               KernelMode,
                               FALSE,
                               NULL );
     }

    //
    //  Verify the completion has actually been run
    //

    ASSERT(KeReadStateEvent(&event) || !NT_SUCCESS(ioStatusBlock.Status));


    if (ARGUMENT_PRESENT(LengthReturned)) {

        *LengthReturned = (ULONG) ioStatusBlock.Information;
    }

    return ioStatusBlock.Status;
}


////////////////////////////////////////////////////////////////////////
//                                                                    //
//         Common attachment and detachment routines                  //
//                                                                    //
////////////////////////////////////////////////////////////////////////

//
//  VERSION NOTE:
//
//  To be able to safely find out if our filter is attached to a device given
//  its name on Windows 2000 and later, we need to use the approach in 
//  SpyIsAttachedToDeviceByUserDeviceName.  This method uses APIs that are
//  available on Windows 2000 and later.  On Windows XP or later, you could
//  change this routine to separate the translation from DeviceName to device
//  object from the search to see if our filter's device is attached to the
//  device stack.  In Windows XP and later, the logic to translate the 
//  DeviceName to the device object is the same, but you can use the logic
//  in SpyIsAttachedToDeviceWXPAndLater to find your filter's device object
//  in the device stack safely.
//

NTSTATUS 
SpyIsAttachedToDeviceByUserDeviceName (
    IN PUNICODE_STRING DeviceName,
    IN OUT PBOOLEAN IsAttached,
    IN OUT PDEVICE_OBJECT *StackDeviceObject,
    IN OUT PDEVICE_OBJECT *OurAttachedDeviceObject
    )
/*++

Routine Description:

    This routine maps a user's device name to a file system device stack, if
    one exists.  Then this routine walks the device stack to find a device
    object belonging to our driver.

    The APIs used here to walk the device stack are all safe to use while you
    are guaranteed that the device stack will not go away.  We enforce this
    guarantee
Arguments:

    DeviceName - The name provided by the user to identify this device.

    IsAttached - This is set to TRUE if our filter is attached to this device
        stack, otherwise this is set to FALSE.

    StackDeviceObject - Set to a device object in the stack identified by the
        DeviceName.  If this is non-NULL, the caller is responsible for removing
        the reference put on this object before it was returned.

    AttachedDeviceObject - Set to the deviceObject which FileSpy has previously 
        attached to the device stack identify by DeviceName.  If this is
        non-NULL, the caller is responsible for removing the reference put on
        this object before it was returned.

Return Value:

    Returns STATUS_SUCCESS if we were able to successfully translate the
    DeviceName into a device stack and return the StackDeviceObject.  If an
    error occurs during the translation of the DeviceName into a device stack,
    the appropriate error code is returned.

--*/
{
    WCHAR nameBuf[DEVICE_NAMES_SZ];
    UNICODE_STRING volumeNameUnicodeString;
    NTSTATUS status;
    OBJECT_ATTRIBUTES objectAttributes;
    IO_STATUS_BLOCK openStatus;
    PFILE_OBJECT volumeFileObject;
    HANDLE fileHandle;
    PDEVICE_OBJECT baseFsDeviceObject;

    PAGED_CODE();

    //
    //  Initialize return state
    //

    ASSERT( NULL != StackDeviceObject );
    ASSERT( NULL != OurAttachedDeviceObject );
    ASSERT( NULL != IsAttached );
    
    *StackDeviceObject = NULL;
    *OurAttachedDeviceObject = NULL;
    *IsAttached = FALSE;

    //
    //  Setup the name to open
    //

    RtlInitEmptyUnicodeString( &volumeNameUnicodeString, nameBuf, sizeof( nameBuf ) );
    RtlAppendUnicodeToString( &volumeNameUnicodeString, L"\\DosDevices\\" );
    RtlAppendUnicodeStringToString( &volumeNameUnicodeString, DeviceName );

    InitializeObjectAttributes( &objectAttributes,
								&volumeNameUnicodeString,
								OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
								NULL,
								NULL);

    //
	// open the file object for the given device
	//

    status = ZwCreateFile( &fileHandle,
						   SYNCHRONIZE|FILE_READ_DATA,
						   &objectAttributes,
						   &openStatus,
						   NULL,
						   0,
						   FILE_SHARE_READ|FILE_SHARE_WRITE,
						   FILE_OPEN,
						   FILE_SYNCHRONOUS_IO_NONALERT,
						   NULL,
						   0);

    if (STATUS_OBJECT_PATH_NOT_FOUND == status ||
        STATUS_OBJECT_NAME_INVALID == status) {

        //
        //  Maybe this name didn't need the "\DosDevices\" prepended to the
        //  name.  Try the open again using just the DeviceName passed in.
        //

         InitializeObjectAttributes( &objectAttributes,
                                     DeviceName,
                                     OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                                     NULL,
                                     NULL);

        //
    	// open the file object for the given device
    	//

        status = ZwCreateFile( &fileHandle,
    						   SYNCHRONIZE|FILE_READ_DATA,
    						   &objectAttributes,
    						   &openStatus,
    						   NULL,
    						   0,
    						   FILE_SHARE_READ|FILE_SHARE_WRITE,
    						   FILE_OPEN,
    						   FILE_SYNCHRONOUS_IO_NONALERT,
    						   NULL,
    						   0);

        if (!NT_SUCCESS( status )) {

            return status;
        }

        //
        //  We were able to open the device using the name passed in, so
        //  now we will fall through and do the rest of this work.
        //

    } else if (!NT_SUCCESS( status )) {

        return status;
    }

	//
    // get a pointer to the volumes file object
	//

    status = ObReferenceObjectByHandle( fileHandle,
										FILE_READ_DATA,
										*IoFileObjectType,
										KernelMode,
										&volumeFileObject,
										NULL);

    if(!NT_SUCCESS( status )) {

        ZwClose( fileHandle );
        return status;
    }

	//
    // Get the device object we want to attach to (parent device object in chain)
	//

    baseFsDeviceObject = IoGetBaseFileSystemDeviceObject( volumeFileObject );
    
    if (baseFsDeviceObject == NULL) {

        ObDereferenceObject( volumeFileObject );
        ZwClose( fileHandle );

        return STATUS_INVALID_DEVICE_STATE;
    }

    //
    //  Now see if we are attached to this device stack.  Note that we need to 
    //  keep this file object open while we do this search to ensure that the 
    //  stack won't get torn down while SpyIsAttachedToDevice does its work.
    //

    *IsAttached = SpyIsAttachedToDevice( baseFsDeviceObject,
                                         OurAttachedDeviceObject );
    
    //
    //  Return the base file system's device object to represent this device
    //  stack even if we didn't find our device object in the stack.
    //

    ObReferenceObject( baseFsDeviceObject );
    *StackDeviceObject = baseFsDeviceObject;

    //
    //  Close our handle
    //

    ObDereferenceObject( volumeFileObject );
    ZwClose( fileHandle );

    return STATUS_SUCCESS;
}

//
//  VERSION NOTE:
//  
//  In Windows 2000, the APIs to safely walk an arbitrary file system device 
//  stack were not supported.  If we can guarantee that a device stack won't 
//  be torn down during the walking of the device stack, we can walk from
//  the base file system's device object up to the top of the device stack
//  to see if we are attached.  We know the device stack will not go away if
//  we are in the process of processing a mount request OR we have a file object
//  open on this device.
//  
//  In Windows XP and later, the IO Manager provides APIs that will allow us to
//  walk through the chain safely using reference counts to protect the device 
//  object from going away while we are inspecting it.  This can be done at any
//  time.
//
//  MULTIVERSION NOTE:
//
//  If built for Windows XP or later, this driver is built to run on 
//  multiple versions.  When this is the case, we will test for the presence of
//  the new IO Manager routines that allow for a filter to safely walk the file
//  system device stack and use those APIs if they are present to determine if
//  we have already attached to this volume.  If these new IO Manager routines
//  are not present, we will assume that we are at the bottom of the file
//  system stack and walk up the stack looking for our device object.
//

BOOLEAN
SpyIsAttachedToDevice (
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PDEVICE_OBJECT *AttachedDeviceObject OPTIONAL
    )
{
    PAGED_CODE();
    
#if WINVER >= 0x0501
    if (IS_WINDOWSXP_OR_LATER()) {

        ASSERT( NULL != gSpyDynamicFunctions.GetLowerDeviceObject &&
                NULL != gSpyDynamicFunctions.GetDeviceAttachmentBaseRef );
        
        return SpyIsAttachedToDeviceWXPAndLater( DeviceObject, AttachedDeviceObject );
    } else {
#endif

        return SpyIsAttachedToDeviceW2K( DeviceObject, AttachedDeviceObject );

#if WINVER >= 0x0501
    }
#endif    
}

BOOLEAN
SpyIsAttachedToDeviceW2K (
    PDEVICE_OBJECT DeviceObject,
    PDEVICE_OBJECT *AttachedDeviceObject OPTIONAL
    )
/*++

Routine Description:

    VERSION: Windows 2000

    This routine walks up the device stack from the DeviceObject passed in
    looking for a device object that belongs to our filter.

    Note:  For this routine to operate safely, the caller must ensure two
        things:
        * the DeviceObject is the base file system's device object and therefore
        is at the bottom of the file system stack
        * this device stack won't be going away while we walk up this stack.  If
        we currently have a file object open for this device stack or we are
        in the process of mounting this device, this guarantee is satisfied.

Arguments:

    DeviceObject - The device chain we want to look through

    AttachedDeviceObject - Set to the deviceObject which FileSpy
            has previously attached to DeviceObject.  If this is non-NULL,
            the caller must clear the reference put on this device object.

Return Value:

    TRUE if we are attached, FALSE if not

--*/
{
    PDEVICE_OBJECT currentDeviceObject;

    PAGED_CODE();

    for (currentDeviceObject = DeviceObject;
         currentDeviceObject != NULL;
         currentDeviceObject = currentDeviceObject->AttachedDevice) {

        if (IS_FILESPY_DEVICE_OBJECT( currentDeviceObject )) {

            //
            //  We are attached.  If requested, return the found device object.
            //

            if (ARGUMENT_PRESENT( AttachedDeviceObject )) {

                ObReferenceObject( currentDeviceObject );
                *AttachedDeviceObject = currentDeviceObject;
            }

            return TRUE;
        }
    }

    //
    //  We did not find ourselves on the attachment chain.  Return a NULL
    //  device object pointer (if requested) and return we did not find
    //  ourselves.
    //
    
    if (ARGUMENT_PRESENT(AttachedDeviceObject)) {

        *AttachedDeviceObject = NULL;
    }

    return FALSE;
}

#if WINVER >= 0x0501

BOOLEAN
SpyIsAttachedToDeviceWXPAndLater (
    PDEVICE_OBJECT DeviceObject,
    PDEVICE_OBJECT *AttachedDeviceObject OPTIONAL
    )
/*++

Routine Description:

    VERSION: Windows XP and later

    This walks down the attachment chain looking for a device object that
    belongs to this driver.  If one is found, the attached device object
    is returned in AttachedDeviceObject.

Arguments:

    DeviceObject - The device chain we want to look through

    AttachedDeviceObject - Set to the deviceObject which FileSpy
            has previously attached to DeviceObject.

Return Value:

    TRUE if we are attached, FALSE if not

--*/
{
    PDEVICE_OBJECT currentDevObj;
    PDEVICE_OBJECT nextDevObj;

    PAGED_CODE();
    
    //
    //  Get the device object at the TOP of the attachment chain
    //

    ASSERT( NULL != gSpyDynamicFunctions.GetAttachedDeviceReference );
    currentDevObj = (gSpyDynamicFunctions.GetAttachedDeviceReference)( DeviceObject );

    //
    //  Scan down the list to find our device object.
    //

    do {
    
        if (IS_FILESPY_DEVICE_OBJECT( currentDevObj )) {

            //
            //  We have found that we are already attached.  If we are
            //  returning the device object, leave it referenced else remove
            //  the reference.
            //

            if (NULL != AttachedDeviceObject) {

                *AttachedDeviceObject = currentDevObj;

            } else {

                ObDereferenceObject( currentDevObj );
            }

            return TRUE;
        }

        //
        //  Get the next attached object.  This puts a reference on 
        //  the device object.
        //

        ASSERT( NULL != gSpyDynamicFunctions.GetLowerDeviceObject );
        nextDevObj = (gSpyDynamicFunctions.GetLowerDeviceObject)( currentDevObj );

        //
        //  Dereference our current device object, before
        //  moving to the next one.
        //

        ObDereferenceObject( currentDevObj );

        currentDevObj = nextDevObj;
        
    } while (NULL != currentDevObj);
    
    //
    //  Mark no device returned
    //

    if (ARGUMENT_PRESENT(AttachedDeviceObject)) {

        *AttachedDeviceObject = NULL;
    }

    return FALSE;
}

#endif //WINVER >= 0x0501

NTSTATUS
SpyAttachToMountedDevice (
    IN PDEVICE_OBJECT DeviceObject,
    IN PDEVICE_OBJECT FilespyDeviceObject
    )
/*++

Routine Description:

    This routine will attach the FileSpyDeviceObject to the filter stack
    that DeviceObject is in.

    NOTE:  If there is an error in attaching, the caller is responsible
        for deleting the FilespyDeviceObject.
    
Arguments:

    DeviceObject - The device object in the stack to which we want to attach.

    FilespyDeviceObject - The filespy device object that is to be attached to
            "DeviceObject".
        
Return Value:

    Returns STATUS_SUCCESS if the filespy deviceObject could be attached,
    otherwise an appropriate error code is returned.
    
--*/
{
    PFILESPY_DEVICE_EXTENSION devExt = FilespyDeviceObject->DeviceExtension;
    NTSTATUS status = STATUS_SUCCESS;
    ULONG i;

    PAGED_CODE();
    ASSERT( IS_FILESPY_DEVICE_OBJECT( FilespyDeviceObject ) );
#if WINVER >= 0x0501    
    ASSERT( !SpyIsAttachedToDevice( DeviceObject, NULL ) );
#endif
    
    //
    //  Insert pointer from extension back to owning device object
    //

    devExt->ThisDeviceObject = FilespyDeviceObject;

    //
    //  Propagate flags from Device Object we are trying to attach to.
    //  Note that we do this before the actual attachment to make sure
    //  the flags are properly set once we are attached (since an IRP
    //  can come in immediately after attachment but before the flags would
    //  be set).
    //

    if (FlagOn( DeviceObject->Flags, DO_BUFFERED_IO )) {

        SetFlag( FilespyDeviceObject->Flags, DO_BUFFERED_IO );
    }

    if (FlagOn( DeviceObject->Flags, DO_DIRECT_IO )) {

        SetFlag( FilespyDeviceObject->Flags, DO_DIRECT_IO );
    }

    //
    //  It is possible for this attachment request to fail because this device
    //  object has not finished initializing.  This can occur if this filter
    //  loaded just as this volume was being mounted.
    //

    for (i=0; i < 8; i++) {
        LARGE_INTEGER interval;

        //
        //  Attach our device object to the given device object
        //  The only reason this can fail is if someone is trying to dismount
        //  this volume while we are attaching to it.
        //

        status = SpyAttachDeviceToDeviceStack( FilespyDeviceObject,
                                               DeviceObject,
                                               &devExt->AttachedToDeviceObject );

        if (NT_SUCCESS(status) ) {

            //
            //  Do all common initializing of the device extension
            //

            SetFlag(devExt->Flags,IsVolumeDeviceObject);

            RtlInitEmptyUnicodeString( &devExt->UserNames,
                                       devExt->UserNamesBuffer,
                                       sizeof(devExt->UserNamesBuffer) );

            SpyInitDeviceNamingEnvironment( FilespyDeviceObject );

            SPY_LOG_PRINT( SPYDEBUG_DISPLAY_ATTACHMENT_NAMES,
                           ("FileSpy!SpyAttachToMountedDevice:            Attaching to volume        %p \"%wZ\"\n",
                            devExt->AttachedToDeviceObject,
                            &devExt->DeviceName) );

            //
            //  Add this device to our attachment list
            //

            ExAcquireFastMutex( &gSpyDeviceExtensionListLock );
            InsertTailList( &gSpyDeviceExtensionList, &devExt->NextFileSpyDeviceLink );
            ExReleaseFastMutex( &gSpyDeviceExtensionListLock );
            SetFlag(devExt->Flags,ExtensionIsLinked);

            return STATUS_SUCCESS;
        }

        //
        //  Delay, giving the device object a chance to finish its
        //  initialization so we can try again
        //

        interval.QuadPart = (500 * DELAY_ONE_MILLISECOND);      //delay 1/2 second
        KeDelayExecutionThread( KernelMode, FALSE, &interval );
    }

    return status;
}


VOID
SpyCleanupMountedDevice (
    IN PDEVICE_OBJECT DeviceObject
    )
/*++

Routine Description:

    This cleans up any allocated memory in the device extension.

Arguments:

    DeviceObject - The device we are cleaning up

Return Value:

--*/
{        
    PFILESPY_DEVICE_EXTENSION devExt = DeviceObject->DeviceExtension;

    PAGED_CODE();
    
    ASSERT(IS_FILESPY_DEVICE_OBJECT( DeviceObject ));

    SpyCleanupDeviceNamingEnvironment( DeviceObject );

    //
    //  Unlink from global list
    //

    if (FlagOn(devExt->Flags,ExtensionIsLinked)) {

        ExAcquireFastMutex( &gSpyDeviceExtensionListLock );
        RemoveEntryList( &devExt->NextFileSpyDeviceLink );
        ExReleaseFastMutex( &gSpyDeviceExtensionListLock );
        ClearFlag(devExt->Flags,ExtensionIsLinked);
    }
}

////////////////////////////////////////////////////////////////////////
//                                                                    //
//                    Start/stop logging routines                     //
//                                                                    //
////////////////////////////////////////////////////////////////////////

//
//  VERSION NOTE:
//
//  On Windows 2000, we will try to attach a new FileSpy device object to the 
//  device stack represented by the DeviceObject parameter.  We cannot get the
//  real disk device at this time, so this field will be set to NULL in the 
//  device extension.  We also cannot get the device name as it is named
//  in the storage stack for this volume (e.g., \Device\HarddiskVolume1), so we 
//  will just use the user's name for the device for our device name.  On
//  Windows 2000, this information is only available as the device mounts.
//
//  On Windows XP and later, we will try to attach a new FileSpy device object
//  to the device stack represented by the DeviceObject parameter.  We are able
//  to get the disk device object for this stack, so that will be appropriately
//  set in the device extension.  We will also be able to get the device name
//  as it is named by the storage stack.
//
//  MULTIVERSION NOTE:
//
//  In SpyAttachToDeviceOnDemand, you see the code to determine which method of
//  determining if we are already attached based on the dynamically loaded
//  functions present.  If this driver is build for Windows 2000 specifically,
//  this logic will not be used.
//

NTSTATUS
SpyAttachToDeviceOnDemand (
    IN PDEVICE_OBJECT DeviceObject,
    IN PUNICODE_STRING UserDeviceName,
    IN OUT PDEVICE_OBJECT *FileSpyDeviceObject
    )
/*++

Routine Description:

    This routine does what is necessary to attach to a device sometime after
    the device has been mounted.

Arguments:

    DeviceObject - The device object that represents the file system stack
        for the volume named by UserDeviceName.

    UserDeviceName - Name of device for which logging should be started

    FileSpyDeviceObject - Set to the new filespy device object that was
        attached if we could successfully attach.
    
Return Value:

    STATUS_SUCCESS if we were able to attach, or an appropriate error code 
    otherwise.
    
--*/
{
    PAGED_CODE();
    
    //
    //  If this device is a DFS device, we do not want to attach to it, so
    //  do this quick check here and return an error if this is the case.
    //
    //  DFS will just redirect the operation to the appropriate redirector.  If
    //  you are interested in monitoring these IOs, you should attach to the 
    //  redirectors.  You cannot attach to these on demand by naming the DFS
    //  device, therefore we fail these requests.
    //

    if (DeviceObject->DeviceType == FILE_DEVICE_DFS) {

        return STATUS_NOT_SUPPORTED;
    }
    
#if WINVER >= 0x0501
    if (IS_WINDOWSXP_OR_LATER()) {

        ASSERT( NULL != gSpyDynamicFunctions.GetDeviceAttachmentBaseRef &&
                NULL != gSpyDynamicFunctions.GetDiskDeviceObject );
        
        return SpyAttachToDeviceOnDemandWXPAndLater( DeviceObject,
                                                     UserDeviceName,
                                                     FileSpyDeviceObject );
    } else {
#endif

        return SpyAttachToDeviceOnDemandW2K( DeviceObject,
                                             UserDeviceName,
                                             FileSpyDeviceObject );
#if WINVER >= 0x0501
    }
#endif    
}

NTSTATUS
SpyAttachToDeviceOnDemandW2K (
    IN PDEVICE_OBJECT DeviceObject,
    IN PUNICODE_STRING UserDeviceName,
    IN OUT PDEVICE_OBJECT *FileSpyDeviceObject
    )
/*++

Routine Description:

    VERSION: Windows 2000
    
    This routine does what is necessary to attach to a device sometime after
    the device has been mounted.

    Note that on Windows 2000, we cannot get the disk device object, therefore
    we will just use the User's device name as our name here.

Arguments:

    DeviceObject - The device object that represents the file system stack
        for the volume named by UserDeviceName.

    UserDeviceName - Name of device for which logging should be started

    FileSpyDeviceObject - Set to the new filespy device object that was
        attached if we could successfully attach.
    
Return Value:

    STATUS_SUCCESS if we were able to attach, or an appropriate error code 
    otherwise.
    
--*/
{
    NTSTATUS status;
    PFILESPY_DEVICE_EXTENSION devExt;

    PAGED_CODE();

    ASSERT( FileSpyDeviceObject != NULL );

    //
    //  Create a new device object so we can attach it in the filter stack
    //
    
    status = IoCreateDevice( gFileSpyDriverObject,
							 sizeof( FILESPY_DEVICE_EXTENSION ),
							 NULL,
							 DeviceObject->DeviceType,
							 0,
							 FALSE,
							 FileSpyDeviceObject );

    if (!NT_SUCCESS( status )) {

        return status;
    }

    //
    //  Set disk device object
    //

    devExt = (*FileSpyDeviceObject)->DeviceExtension;
    devExt->Flags = 0;

    //
    //  We cannot get the disk device object when we attach on demand in W2K.
    //
    
    devExt->DiskDeviceObject = NULL;

    //
    //  Set Device Name, we will just use the user's device name on W2K.
    //

    RtlInitEmptyUnicodeString( &devExt->DeviceName,
                               devExt->DeviceNameBuffer,
                               sizeof(devExt->DeviceNameBuffer) );

    RtlAppendUnicodeStringToString( &devExt->DeviceName,
                                    UserDeviceName );
  
    //
    //  Call the routine to attach to a mounted device.
    //

    status = SpyAttachToMountedDevice( DeviceObject,
                                       *FileSpyDeviceObject );

    if (!NT_SUCCESS( status )) {

        SPY_LOG_PRINT( SPYDEBUG_ERROR,
                       ("FileSpy!SpyStartLoggingDevice: Could not attach to \"%wZ\"; logging not started.\n",
                        UserDeviceName) );

        SpyCleanupMountedDevice( *FileSpyDeviceObject );
        IoDeleteDevice( *FileSpyDeviceObject );
        *FileSpyDeviceObject = NULL;
    }

    return status;
}

#if WINVER >= 0x0501

NTSTATUS
SpyAttachToDeviceOnDemandWXPAndLater (
    IN PDEVICE_OBJECT DeviceObject,
    IN PUNICODE_STRING UserDeviceName,
    IN OUT PDEVICE_OBJECT *FileSpyDeviceObject
    )
/*++

Routine Description:

    This routine does what is necessary to attach to a device sometime after
    the device has been mounted.

Arguments:

    DeviceObject - The device object that represents the file system stack
        for the volume named by UserDeviceName.

    UserDeviceName - Name of device for which logging should be started

    FileSpyDeviceObject - Set to the new filespy device object that was
        attached if we could successfully attach.
    
Return Value:

    STATUS_SUCCESS if we were able to attach, or an appropriate error code 
    otherwise.
    
--*/
{

    NTSTATUS status;
    PFILESPY_DEVICE_EXTENSION devExt;
    PDEVICE_OBJECT baseFileSystemDeviceObject = NULL;
    PDEVICE_OBJECT diskDeviceObject = NULL;

    PAGED_CODE();

    UNREFERENCED_PARAMETER( UserDeviceName );
    ASSERT( FileSpyDeviceObject != NULL );

    //
    //  If this is a network file system, there will not be a disk device
    //  associated with this device, so there is no need to make this request
    //  of the IO Manager.  We will get the name of the network file system
    //  later from the baseFileSystemDeviceObject vs. the diskDeviceObject 
    //  which is used to retrieve the device name for local volumes.
    //

    baseFileSystemDeviceObject = (gSpyDynamicFunctions.GetDeviceAttachmentBaseRef)( DeviceObject );

    if (FILE_DEVICE_NETWORK_FILE_SYSTEM != baseFileSystemDeviceObject->DeviceType) {

        //
        //  If this is not a network file system, query the IO Manager to get
        //  the diskDeviceObject.  We will only attach if this device has a
        //  disk device object.
        //
        //  It may not have a disk device object for the following reasons:
        //  - It is a control device object for a driver
        //  - There is no media in the device.
        //

        status = (gSpyDynamicFunctions.GetDiskDeviceObject)( baseFileSystemDeviceObject, 
                                                            &diskDeviceObject );

        if (!NT_SUCCESS( status )) {

            SPY_LOG_PRINT( SPYDEBUG_ERROR,
                           ("FileSpy!SpyStartLoggingDevice: No disk device object exists for \"%wZ\"; cannot log this volume.\n",
                            UserDeviceName) );

            goto SpyAttachToDeviceOnDemand_Exit;
        }
    }
    
    //
    //  Create a new device object so we can attach it in the filter stack
    //
    
    status = IoCreateDevice( gFileSpyDriverObject,
							 sizeof( FILESPY_DEVICE_EXTENSION ),
							 NULL,
							 DeviceObject->DeviceType,
							 0,
							 FALSE,
							 FileSpyDeviceObject );

    if (!NT_SUCCESS( status )) {

        goto SpyAttachToDeviceOnDemand_Exit;
    }

    //
    //  Set disk device object
    //

    devExt = (*FileSpyDeviceObject)->DeviceExtension;
    devExt->Flags = 0;

    devExt->DiskDeviceObject = diskDeviceObject;

    //
    //  Set Device Name
    //

    RtlInitEmptyUnicodeString( &devExt->DeviceName,
                               devExt->DeviceNameBuffer,
                               sizeof(devExt->DeviceNameBuffer) );

    if (NULL != diskDeviceObject) {
        
        SpyGetObjectName( diskDeviceObject, 
                          &devExt->DeviceName );

    } else {

        ASSERT( NULL != baseFileSystemDeviceObject &&
                FILE_DEVICE_NETWORK_FILE_SYSTEM == baseFileSystemDeviceObject->DeviceType);

        SpyGetObjectName( baseFileSystemDeviceObject,
                          &devExt->DeviceName );
    }

    //
    //  Call the routine to attach to a mounted device.
    //

    status = SpyAttachToMountedDevice( DeviceObject,
                                       *FileSpyDeviceObject );


    if (!NT_SUCCESS( status )) {

        SPY_LOG_PRINT( SPYDEBUG_ERROR,
                       ("FileSpy!SpyStartLoggingDevice: Could not attach to \"%wZ\"; logging not started.\n",
                        UserDeviceName) );

        SpyCleanupMountedDevice( *FileSpyDeviceObject );
        IoDeleteDevice( *FileSpyDeviceObject );
        *FileSpyDeviceObject = NULL;
        goto SpyAttachToDeviceOnDemand_Exit;
    }

SpyAttachToDeviceOnDemand_Exit:

    if (NULL != baseFileSystemDeviceObject) {

        ObDereferenceObject( baseFileSystemDeviceObject );
    }

    if (NULL != diskDeviceObject) {

        ObDereferenceObject( diskDeviceObject );
    }
    
    return status;
}

#endif    

NTSTATUS
SpyStartLoggingDevice (
    IN PWSTR UserDeviceName
    )
/*++

Routine Description:

    This routine ensures that we are attached to the specified device
    then turns on logging for that device.
    
    Note:  Since all network drives through LAN Manager are represented by _
        one_ device object, we want to only attach to this device stack once
        and use only one device extension to represent all these drives.
        Since FileSpy does not do anything to filter I/O on the LAN Manager's
        device object to only log the I/O to the requested drive, the user
        will see all I/O to a network drive it he/she is attached to a
        network drive.

Arguments:

    UserDeviceName - Name of device for which logging should be started
    
Return Value:

    STATUS_SUCCESS if the logging has been successfully started, or
    an appropriate error code if the logging could not be started.
    
--*/
{
    UNICODE_STRING userDeviceName;
    NTSTATUS status;
    PFILESPY_DEVICE_EXTENSION devExt;
    BOOLEAN isAttached = FALSE;
    PDEVICE_OBJECT stackDeviceObject;
    PDEVICE_OBJECT filespyDeviceObject;

    PAGED_CODE();
    
    //
    //  Check to see if we have previously attached to this device by
    //  opening this device name then looking through its list of attached
    //  devices.
    //

    RtlInitUnicodeString( &userDeviceName, UserDeviceName );

    status = SpyIsAttachedToDeviceByUserDeviceName( &userDeviceName,
                                                    &isAttached,
                                                    &stackDeviceObject,
                                                    &filespyDeviceObject );

    if (!NT_SUCCESS( status )) {

        //
        //  There was an error, so return the error code.
        //
        
        return status;
    }
        
    if (isAttached) {

        //
        //  We are already attached, so just make sure that logging is turned on
        //  for this device.
        //

        ASSERT( NULL != filespyDeviceObject );

        devExt = filespyDeviceObject->DeviceExtension;
        SetFlag(devExt->Flags,LogThisDevice);

        SpyStoreUserName( devExt, &userDeviceName );

        //
        //  Clear the reference that was returned from SpyIsAttachedToDevice.
        //
        
        ObDereferenceObject( filespyDeviceObject );
        
    } else {

        status = SpyAttachToDeviceOnDemand( stackDeviceObject,
                                            &userDeviceName,
                                            &filespyDeviceObject );

        if (!NT_SUCCESS( status )) {

            ObDereferenceObject( stackDeviceObject );
            return status;
        }

        ASSERT( filespyDeviceObject != NULL );
        
        devExt = filespyDeviceObject->DeviceExtension;

        //
        //  We successfully attached so finish our device extension 
        //  initialization.  Along this code path, we want to turn on
        //  logging and store our device name.
        // 

        SetFlag(devExt->Flags,LogThisDevice);

        //
        //  We want to store the name that was used by the user-mode
        //  application to name this device.
        //

        SpyStoreUserName( devExt, &userDeviceName );

        //
        //
        //  Finished all initialization of the new device object,  so clear the
        //  initializing flag now.  This allows other filters to now attach
        //  to our device object.
        //
        //

        ClearFlag( filespyDeviceObject->Flags, DO_DEVICE_INITIALIZING );

    }

    ObDereferenceObject( stackDeviceObject );
    return STATUS_SUCCESS;
}

NTSTATUS
SpyStopLoggingDevice (
    IN PWSTR DeviceName
    )
/*++

Routine Description:

    This routine stop logging the specified device.  Since you can not
    physically detach from devices, this routine simply sets a flag saying
    to not log the device anymore.

    Note:  Since all network drives are represented by _one_ device object,
        and, therefore, one device extension, if the user detaches from one
        network drive, it has the affect of detaching from _all_ network
        devices.

Arguments:

    DeviceName - The name of the device to stop logging.

Return Value:
    NT Status code

--*/
{
    WCHAR nameBuf[DEVICE_NAMES_SZ];
    UNICODE_STRING volumeNameUnicodeString;
    PDEVICE_OBJECT deviceObject;
    PDEVICE_OBJECT filespyDeviceObject;
    BOOLEAN isAttached = FALSE;
    PFILESPY_DEVICE_EXTENSION devExt;
    NTSTATUS status;

    PAGED_CODE();
    
    RtlInitEmptyUnicodeString( &volumeNameUnicodeString, nameBuf, sizeof( nameBuf ) );
    RtlAppendUnicodeToString( &volumeNameUnicodeString, DeviceName );

    status = SpyIsAttachedToDeviceByUserDeviceName( &volumeNameUnicodeString, 
                                                    &isAttached,
                                                    &deviceObject,
                                                    &filespyDeviceObject );

    if (!NT_SUCCESS( status )) {

        //
        //  We could not get the deviceObject from this DeviceName, so
        //  return the error code.
        //
        
        return status;
    }

    //
    //  Find Filespy's device object from the device stack to which
    //  deviceObject is attached.
    //

    if (isAttached) {

        //
        //  FileSpy is attached and FileSpy's deviceObject was returned.
        //

        ASSERT( NULL != filespyDeviceObject );

        devExt = filespyDeviceObject->DeviceExtension;

        //
        //  Stop logging
        //

        ClearFlag(devExt->Flags,LogThisDevice);

        status = STATUS_SUCCESS;

        ObDereferenceObject( filespyDeviceObject );

    } else {

        status = STATUS_INVALID_PARAMETER;
    }    

    ObDereferenceObject( deviceObject );

    return status;
}

////////////////////////////////////////////////////////////////////////
//                                                                    //
//       Attaching/detaching to all volumes in system routines        //
//                                                                    //
////////////////////////////////////////////////////////////////////////

NTSTATUS
SpyAttachToFileSystemDevice (
    IN PDEVICE_OBJECT DeviceObject,
    IN PUNICODE_STRING DeviceName
    )
/*++

Routine Description:

    This will attach to the given file system device object.  We attach to
    these devices so we will know when new devices are mounted.

Arguments:

    DeviceObject - The device to attach to

    DeviceName - Contains the name of this device.

Return Value:

    Status of the operation

--*/
{
    PDEVICE_OBJECT filespyDeviceObject;
    PFILESPY_DEVICE_EXTENSION devExt;
    UNICODE_STRING fsrecName;
    NTSTATUS status;
    UNICODE_STRING tempName;
    WCHAR tempNameBuffer[DEVICE_NAMES_SZ];

    PAGED_CODE();

    //
    //  See if this is a file system we care about.  If not, return.
    //

    if (!IS_SUPPORTED_DEVICE_TYPE(DeviceObject->DeviceType)) {

        return STATUS_SUCCESS;
    }

    //
    //  See if this is Microsoft's file system recognizer device (see if the name of the
    //  driver is the FS_REC driver).  If so skip it.  We don't need to 
    //  attach to file system recognizer devices since we can just wait for the
    //  real file system driver to load.  Therefore, if we can identify them, we won't
    //  attach to them.
    //

    RtlInitUnicodeString( &fsrecName, L"\\FileSystem\\Fs_Rec" );

    RtlInitEmptyUnicodeString( &tempName,
                               tempNameBuffer,
                               sizeof(tempNameBuffer) );

    SpyGetObjectName( DeviceObject->DriverObject, &tempName );
    
    if (RtlCompareUnicodeString( &tempName, &fsrecName, TRUE ) == 0) {

        return STATUS_SUCCESS;
    }

    //
    //  Create a new device object we can attach with
    //

    status = IoCreateDevice( gFileSpyDriverObject,
                             sizeof( FILESPY_DEVICE_EXTENSION ),
                             (PUNICODE_STRING) NULL,
                             DeviceObject->DeviceType,
                             0,
                             FALSE,
                             &filespyDeviceObject );

    if (!NT_SUCCESS( status )) {

        SPY_LOG_PRINT( SPYDEBUG_ERROR,
                       ("FileSpy!SpyAttachToFileSystemDevice: Error creating volume device object for \"%wZ\", status=%08x\n",
                        DeviceName,
                        status) );
        return status;
    }

    //
    //  Load extension, set device object associated with extension
    //

    devExt = filespyDeviceObject->DeviceExtension;
    devExt->Flags = 0;

    devExt->ThisDeviceObject = filespyDeviceObject;

    //
    //  Propagate flags from Device Object we are trying to attach to.
    //  Note that we do this before the actual attachment to make sure
    //  the flags are properly set once we are attached (since an IRP
    //  can come in immediately after attachment but before the flags would
    //  be set).
    //

    if ( FlagOn( DeviceObject->Flags, DO_BUFFERED_IO )) {

        SetFlag( filespyDeviceObject->Flags, DO_BUFFERED_IO );
    }

    if ( FlagOn( DeviceObject->Flags, DO_DIRECT_IO )) {

        SetFlag( filespyDeviceObject->Flags, DO_DIRECT_IO );
    }

    if ( FlagOn( DeviceObject->Characteristics, FILE_DEVICE_SECURE_OPEN ) ) {

        SetFlag( filespyDeviceObject->Characteristics, FILE_DEVICE_SECURE_OPEN );
    }

    //
    //  Do the attachment
    //

    status = SpyAttachDeviceToDeviceStack( filespyDeviceObject, 
                                           DeviceObject, 
                                           &devExt->AttachedToDeviceObject );

    if (!NT_SUCCESS( status )) {

        SPY_LOG_PRINT( SPYDEBUG_ERROR,
                       ("FileSpy!SpyAttachToFileSystemDevice: Could not attach FileSpy to the filesystem control device object \"%wZ\".\n",
                        DeviceName) );

        goto ErrorCleanupDevice;
    }

    //
    //  Since this is an attachment to a file system control device object
    //  we are not going to log anything, but properly initialize our
    //  extension.
    //

    RtlInitEmptyUnicodeString( &devExt->DeviceName,
                               devExt->DeviceNameBuffer,
                               sizeof(devExt->DeviceNameBuffer) );

    RtlCopyUnicodeString( &devExt->DeviceName, DeviceName );        //Save Name

    RtlInitEmptyUnicodeString( &devExt->UserNames,
                               devExt->UserNamesBuffer,
                               sizeof(devExt->UserNamesBuffer) );
                               
    SpyInitDeviceNamingEnvironment( filespyDeviceObject );

    //
    //  The NETWORK device objects function as both CDOs (control device object)
    //  and VDOs (volume device object) so insert the NETWORK CDO devices into
    //  the list of attached device so we will properly enumerate it.
    //

    if (FILE_DEVICE_NETWORK_FILE_SYSTEM == DeviceObject->DeviceType) {

        ExAcquireFastMutex( &gSpyDeviceExtensionListLock );
        InsertTailList( &gSpyDeviceExtensionList, &devExt->NextFileSpyDeviceLink );
        ExReleaseFastMutex( &gSpyDeviceExtensionListLock );
        SetFlag(devExt->Flags,ExtensionIsLinked);
    }

    //
    //  Flag we are no longer initializing this device object
    //

    ClearFlag( filespyDeviceObject->Flags, DO_DEVICE_INITIALIZING );

    //
    //  Display who we have attached to
    //

    SPY_LOG_PRINT( SPYDEBUG_DISPLAY_ATTACHMENT_NAMES,
                   ("FileSpy!SpyAttachToFileSystemDevice:         Attaching to file system   %p \"%wZ\" (%s)\n",
                    DeviceObject,
                    &devExt->DeviceName,
                    GET_DEVICE_TYPE_NAME(filespyDeviceObject->DeviceType)) );

    //
    //  VERSION NOTE:
    //
    //  In Windows XP, the IO Manager provided APIs to safely enumerate all the
    //  device objects for a given driver.  This allows filters to attach to 
    //  all mounted volumes for a given file system at some time after the
    //  volume has been mounted.  There is no support for this functionality
    //  in Windows 2000.
    //
    //  MULTIVERSION NOTE:
    //
    //  If built for Windows XP or later, this driver is built to run on 
    //  multiple versions.  When this is the case, we will test
    //  for the presence of the new IO Manager routines that allow for volume 
    //  enumeration.  If they are not present, we will not enumerate the volumes
    //  when we attach to a new file system.
    //
    
#if WINVER >= 0x0501

    if (IS_WINDOWSXP_OR_LATER()) {

        ASSERT( NULL != gSpyDynamicFunctions.EnumerateDeviceObjectList &&
                NULL != gSpyDynamicFunctions.GetDiskDeviceObject &&
                NULL != gSpyDynamicFunctions.GetDeviceAttachmentBaseRef &&
                NULL != gSpyDynamicFunctions.GetLowerDeviceObject );

        //
        //  Enumerate all the mounted devices that currently
        //  exist for this file system and attach to them.
        //

        status = SpyEnumerateFileSystemVolumes( DeviceObject, &tempName );

        if (!NT_SUCCESS( status )) {

            SPY_LOG_PRINT( SPYDEBUG_ERROR,
                           ("FileSpy!SpyAttachToFileSystemDevice: Error attaching to existing volumes for \"%wZ\", status=%08x\n",
                            DeviceName,
                            status) );

            IoDetachDevice( devExt->AttachedToDeviceObject );
            goto ErrorCleanupDevice;
        }
    }
    
#endif

    return STATUS_SUCCESS;

    /////////////////////////////////////////////////////////////////////
    //                  Cleanup error handling
    /////////////////////////////////////////////////////////////////////

    ErrorCleanupDevice:
        SpyCleanupMountedDevice( filespyDeviceObject );
        IoDeleteDevice( filespyDeviceObject );

    return status;
}

VOID
SpyDetachFromFileSystemDevice (
    IN PDEVICE_OBJECT DeviceObject
    )
/*++

Routine Description:

    Given a base file system device object, this will scan up the attachment
    chain looking for our attached device object.  If found it will detach
    us from the chain.

Arguments:

    DeviceObject - The file system device to detach from.

Return Value:

--*/ 
{
    PDEVICE_OBJECT ourAttachedDevice;
    PFILESPY_DEVICE_EXTENSION devExt;

    PAGED_CODE();

    //
    //  We have to iterate through the device objects in the filter stack
    //  attached to DeviceObject.  If we are attached to this filesystem device
    //  object, We should be at the top of the stack, but there is no guarantee.
    //  If we are in the stack and not at the top, we can safely call IoDetachDevice
    //  at this time because the IO Manager will only really detach our device
    //  object from the stack at a safe time.
    //

    //
    //  Skip the base file system device object (since it can't be us)
    //

    ourAttachedDevice = DeviceObject->AttachedDevice;

    while (NULL != ourAttachedDevice) {

        if (IS_FILESPY_DEVICE_OBJECT( ourAttachedDevice )) {

            devExt = ourAttachedDevice->DeviceExtension;

            //
            //  Display who we detached from
            //

            SPY_LOG_PRINT( SPYDEBUG_DISPLAY_ATTACHMENT_NAMES,
                           ("FileSpy!SpyDetachFromFileSystem:             Detaching from file system %p \"%wZ\" (%s)\n",
                            devExt->AttachedToDeviceObject,
                            &devExt->DeviceName,
                            GET_DEVICE_TYPE_NAME(ourAttachedDevice->DeviceType)) );
                                
            //
            //  Unlink from global list
            //

            if (FlagOn(devExt->Flags,ExtensionIsLinked)) {

                ExAcquireFastMutex( &gSpyDeviceExtensionListLock );
                RemoveEntryList( &devExt->NextFileSpyDeviceLink );
                ExReleaseFastMutex( &gSpyDeviceExtensionListLock );
                ClearFlag(devExt->Flags,ExtensionIsLinked);
            }

            //
            //  Detach us from the object just below us
            //  Cleanup and delete the object
            //

            SpyCleanupMountedDevice( ourAttachedDevice );
            IoDetachDevice( DeviceObject );
            IoDeleteDevice( ourAttachedDevice );

            return;
        }

        //
        //  Look at the next device up in the attachment chain
        //

        DeviceObject = ourAttachedDevice;
        ourAttachedDevice = ourAttachedDevice->AttachedDevice;
    }
}

#if WINVER >= 0x0501

NTSTATUS
SpyEnumerateFileSystemVolumes (
    IN PDEVICE_OBJECT FSDeviceObject,
    IN PUNICODE_STRING Name
    ) 
/*++

Routine Description:

    Enumerate all the mounted devices that currently exist for the given file
    system and attach to them.  We do this because this filter could be loaded
    at any time and there might already be mounted volumes for this file system.

Arguments:

    FSDeviceObject - The device object for the file system we want to enumerate

    Name - An already initialized unicode string used to retrieve names

Return Value:

    The status of the operation

--*/
{
    PDEVICE_OBJECT newDeviceObject;
    PFILESPY_DEVICE_EXTENSION newDevExt;
    PDEVICE_OBJECT *devList;
    PDEVICE_OBJECT diskDeviceObject;
    NTSTATUS status;
    ULONG numDevices;
    ULONG i;

    PAGED_CODE();

    //
    //  Find out how big of an array we need to allocate for the
    //  mounted device list.
    //

    ASSERT( NULL != gSpyDynamicFunctions.EnumerateDeviceObjectList );
    status = (gSpyDynamicFunctions.EnumerateDeviceObjectList)( FSDeviceObject->DriverObject,
                                                              NULL,
                                                              0,
                                                              &numDevices);

    //
    //  We only need to get this list of there are devices.  If we
    //  don't get an error there are no devices so go on.
    //

    if (!NT_SUCCESS( status )) {

        ASSERT(STATUS_BUFFER_TOO_SMALL == status);

        //
        //  Allocate memory for the list of known devices
        //

        numDevices += 8;        //grab a few extra slots

        devList = ExAllocatePoolWithTag( NonPagedPool, 
                                         (numDevices * sizeof(PDEVICE_OBJECT)), 
                                         FILESPY_POOL_TAG );
        if (NULL == devList) {

            return STATUS_INSUFFICIENT_RESOURCES;
        }

        //
        //  Now get the list of devices.  If we get an error again
        //  something is wrong, so just fail.
        //

        status = (gSpyDynamicFunctions.EnumerateDeviceObjectList)(
                        FSDeviceObject->DriverObject,
                        devList,
                        (numDevices * sizeof(PDEVICE_OBJECT)),
                        &numDevices);

        if (!NT_SUCCESS( status ))  {

            ExFreePoolWithTag( devList, FILESPY_POOL_TAG );
            return status;
        }

        //
        //  Walk the given list of devices and attach to them if we should.
        //

        for (i=0; i < numDevices; i++) {

            //
            //  Do not attach if:
            //      - This is the control device object (the one passed in)
            //      - The device type does not match
            //      - We are already attached to it
            //

            if ((devList[i] != FSDeviceObject) && 
                (devList[i]->DeviceType == FSDeviceObject->DeviceType) &&
                !SpyIsAttachedToDevice( devList[i], NULL )) {

                //
                //  See if this device has a name.  If so, then it must
                //  be a control device so don't attach to it.  This handles
                //  drivers with more then one control device.
                //

                SpyGetBaseDeviceObjectName( devList[i], Name );

                if (Name->Length <= 0) {

                    //
                    //  Get the disk device object associated with this
                    //  file  system device object.  Only try to attach if we
                    //  have a disk device object.
                    //

                    ASSERT( NULL != gSpyDynamicFunctions.GetDiskDeviceObject );
                    status = (gSpyDynamicFunctions.GetDiskDeviceObject)( devList[i], &diskDeviceObject );

                    if (NT_SUCCESS( status )) {

                        //
                        //  Allocate a new device object to attach with
                        //

                        status = IoCreateDevice( gFileSpyDriverObject,
                                                 sizeof( FILESPY_DEVICE_EXTENSION ),
                                                 (PUNICODE_STRING) NULL,
                                                 devList[i]->DeviceType,
                                                 0,
                                                 FALSE,
                                                 &newDeviceObject );

                        if (NT_SUCCESS( status )) {

                            //
                            //  Set disk device object
                            //

                            newDevExt = newDeviceObject->DeviceExtension;
                            newDevExt->Flags = 0;

                            newDevExt->DiskDeviceObject = diskDeviceObject;
                    
                            //
                            //  Set Device Name
                            //

                            RtlInitEmptyUnicodeString( &newDevExt->DeviceName,
                                                       newDevExt->DeviceNameBuffer,
                                                       sizeof(newDevExt->DeviceNameBuffer) );

                            SpyGetObjectName( diskDeviceObject, 
                                              &newDevExt->DeviceName );

                            //
                            //  We have done a lot of work since the last time
                            //  we tested to see if we were already attached
                            //  to this device object.  Test again, this time
                            //  with a lock, and attach if we are not attached.
                            //  The lock is used to atomically test if we are
                            //  attached, and then do the attach.
                            //

                            ExAcquireFastMutex( &gSpyAttachLock );

                            if (!SpyIsAttachedToDevice( devList[i], NULL )) {

                                //
                                //  Attach to this device object
                                //

                                status = SpyAttachToMountedDevice( devList[i], 
                                                                   newDeviceObject );

                                //
                                //  Handle normal vs error cases, but keep going
                                //

                                if (NT_SUCCESS( status )) {

                                    //
                                    //  Finished all initialization of the new
                                    //  device object,  so clear the initializing
                                    //  flag now.  This allows other filters to
                                    //  now attach to our device object.
                                    //

                                    ClearFlag( newDeviceObject->Flags, DO_DEVICE_INITIALIZING );

                                } else {

                                    //
                                    //  The attachment failed, cleanup.  Note that
                                    //  we continue processing so we will cleanup
                                    //  the reference counts and try to attach to
                                    //  the rest of the volumes.
                                    //
                                    //  One of the reasons this could have failed
                                    //  is because this volume is just being
                                    //  mounted as we are attaching and the
                                    //  DO_DEVICE_INITIALIZING flag has not yet
                                    //  been cleared.  A filter could handle
                                    //  this situation by pausing for a short
                                    //  period of time and retrying the attachment.
                                    //

                                    SpyCleanupMountedDevice( newDeviceObject );
                                    IoDeleteDevice( newDeviceObject );
                                }

                            } else {

                                //
                                //  We were already attached, cleanup this
                                //  device object.
                                //

                                SpyCleanupMountedDevice( newDeviceObject );
                                IoDeleteDevice( newDeviceObject );
                            }

                            //
                            //  Release the lock
                            //

                            ExReleaseFastMutex( &gSpyAttachLock );

                        } else {

                            SPY_LOG_PRINT( SPYDEBUG_ERROR,
                                           ("FileSpy!SpyEnumberateFileSystemVolumes: Error creating volume device object, status=%08x\n",
                                            status) );
                        }
                        
                        //
                        //  Remove reference added by IoGetDiskDeviceObject.
                        //  We only need to hold this reference until we are
                        //  successfully attached to the current volume.  Once
                        //  we are successfully attached to devList[i], the
                        //  IO Manager will make sure that the underlying
                        //  diskDeviceObject will not go away until the file
                        //  system stack is torn down.
                        //

                        ObDereferenceObject( diskDeviceObject );
                    }
                }
            }

            //
            //  Dereference the object (reference added by 
            //  IoEnumerateDeviceObjectList)
            //

            ObDereferenceObject( devList[i] );
        }

        //
        //  We are going to ignore any errors received while loading.  We
        //  simply won't be attached to those volumes if we get an error
        //

        status = STATUS_SUCCESS;

        //
        //  Free the memory we allocated for the list
        //

        ExFreePoolWithTag( devList, FILESPY_POOL_TAG );
    }

    return status;
}
#endif

////////////////////////////////////////////////////////////////////////
//                                                                    //
//             Private Filespy IOCTLs helper routines                 //
//                                                                    //
////////////////////////////////////////////////////////////////////////

NTSTATUS
SpyGetAttachList (
    IN PVOID Buffer,
    IN ULONG BufferSize,
    OUT PULONG_PTR ReturnLength
    )
/*++

Routine Description:
    This returns an array of structure identifying all of the devices
    we are currently physical attached to and whether logging is on or
    off for the given device

Arguments:
    buffer - buffer to receive the attachment list
    bufferSize - total size in bytes of the return buffer
    returnLength - receives number of bytes we actually return

Return Value:
    NT Status code

--*/
{
    PLIST_ENTRY link;
    PFILESPY_DEVICE_EXTENSION devExt;
    PATTACHED_DEVICE pAttDev;
    ULONG retlen = 0;
    UNICODE_STRING attachedDevName;

    PAGED_CODE();

    pAttDev = Buffer;

    __try {

        ExAcquireFastMutex( &gSpyDeviceExtensionListLock );

        for (link = gSpyDeviceExtensionList.Flink;
             link != &gSpyDeviceExtensionList;
             link = link->Flink) {

            devExt = CONTAINING_RECORD(link, FILESPY_DEVICE_EXTENSION, NextFileSpyDeviceLink);

            if (BufferSize < sizeof(ATTACHED_DEVICE)) {

                break;
    		}

            pAttDev->LoggingOn = BooleanFlagOn(devExt->Flags,LogThisDevice);

            //
            //  We set up a unicode string to represent the buffer where we 
            //  want to copy the device name.  We will reserve space for the 
            //  terminating NULL that the caller is expecting.
            //
            //  NOTE: Since DeviceNames is an imbedded array in the
            //  ATTACHED_DEVICE structure, sizeof( pAttDev->DeviceNames ) 
            //  returns the correct size.  RtlCopyUnicodeString ensure that the
            //  copy does not extend past the MaximumLength of our destination
            //  string.
            //
            
            attachedDevName.MaximumLength = sizeof( pAttDev->DeviceNames ) - 
                                            sizeof( UNICODE_NULL );
            attachedDevName.Length = 0;
            attachedDevName.Buffer = pAttDev->DeviceNames;

            RtlCopyUnicodeString( &attachedDevName,
                                  &devExt->DeviceName );

            attachedDevName.Buffer[attachedDevName.Length/sizeof(WCHAR)] = UNICODE_NULL;

            retlen += sizeof( ATTACHED_DEVICE );
            BufferSize -= sizeof( ATTACHED_DEVICE );
            pAttDev++;
        }
             
    } __finally {

        ExReleaseFastMutex( &gSpyDeviceExtensionListLock );
    }

    *ReturnLength = retlen;
    return STATUS_SUCCESS;
}

VOID
SpyGetLog (
    OUT PVOID            OutputBuffer,
    IN  ULONG            OutputBufferLength,
    OUT PIO_STATUS_BLOCK IoStatus
    )
/*++

Routine Description:
    This function fills OutputBuffer with as many LOG_RECORDs as possible.
    The LOG_RECORDs are variable sizes and are tightly packed in the
    OutputBuffer.

Arguments:
    OutputBuffer - the user's buffer to fill with the log data we have
        collected
    OutputBufferLength - the size in bytes of OutputBuffer
    IoStatus - is set to the correct return status information for this
        operation

Return Value:
    None

--*/
{
    PLIST_ENTRY pList = NULL;
    ULONG length = OutputBufferLength;
    PCHAR pOutBuffer = OutputBuffer;
    PLOG_RECORD pLogRecord = NULL;
    ULONG recordsAvailable = 0, logRecordLength;
    PRECORD_LIST pRecordList;
    KIRQL oldIrql;

    IoStatus->Information = 0;

    KeAcquireSpinLock(&gOutputBufferLock, &oldIrql);

    while (!IsListEmpty( &gOutputBufferList ) && (length > 0)) {

        pList = RemoveHeadList( &gOutputBufferList );

        pRecordList = CONTAINING_RECORD( pList, RECORD_LIST, List );

        pLogRecord = &pRecordList->LogRecord;

        recordsAvailable++;

		//
		//  Pack log records on PVOID boundaries to avoid alignment faults when accessing 
        //  the packed buffer on 64-bit architectures
        // 

        logRecordLength = ROUND_TO_SIZE( pLogRecord->Length, sizeof( PVOID ) );

        if (length < logRecordLength) {

            InsertHeadList( &gOutputBufferList, pList );
            break;
        }

        KeReleaseSpinLock( &gOutputBufferLock, oldIrql );

        //
        //  We wrap these logic in a try/finally because it is possible that our
        //  output buffer is invalid if we are coming through the FastIO path to
        //  service the FILESPY_GetLog IOCTLs.  If this is the case, the 
        //  exception will be caught in SpyFastIoDeviceControl, but we want to 
        //  make sure that we don't lose this current record we are dealing
        //  with.
        //
        
        __try {

            //
            //  Copy of course the non-padded number of bytes
            //

            RtlCopyMemory( pOutBuffer, pLogRecord, pLogRecord->Length );

            //
            //  Adjust the log-record length to the padded length in the copied record 
            //

            ((PLOG_RECORD) pOutBuffer)->Length = logRecordLength;

            IoStatus->Information += logRecordLength;
           
            length -= logRecordLength;

            pOutBuffer += logRecordLength;

            SpyFreeRecord( pRecordList );
            pRecordList = NULL;
            
        } __finally {

            if (pRecordList != NULL) {

                //
                //  We did not successfully process this record, so put it back
                //  on the gOutputBufferList.  The log records contain a sequence
                //  number, so the fact that this may disrupt the ordering of the
                //  log records is not a problem.
                //

                KeAcquireSpinLock( &gOutputBufferLock, &oldIrql );
                InsertHeadList( &gOutputBufferList, &pRecordList->List );
                KeReleaseSpinLock( &gOutputBufferLock, oldIrql );
            }
        }

        KeAcquireSpinLock( &gOutputBufferLock, &oldIrql );
    }

    KeReleaseSpinLock( &gOutputBufferLock, oldIrql );

	//
    // no copies occurred
	//

    if (length == OutputBufferLength && recordsAvailable > 0) {

        IoStatus->Status = STATUS_BUFFER_TOO_SMALL;
    }

    return;
}

VOID
SpyCloseControlDevice (
    )
/*++

Routine Description:

    This is the routine that is associated with IRP_MJ_
    This routine does the cleanup involved in closing the ControlDevice.
    On the close of the Control Device, we need to empty the queue of
    logRecords that are waiting to be returned to the user.

Arguments:

    None.

Return Value:

    None.

--*/
{
    PLIST_ENTRY pList;
    PRECORD_LIST pRecordList;
    KIRQL oldIrql;

    //
    // Set the gControlDeviceState to CLEANING_UP so that we can
    // signal that we are cleaning up the device.
    //

    KeAcquireSpinLock( &gControlDeviceStateLock, &oldIrql );
    gControlDeviceState = CLEANING_UP;
    KeReleaseSpinLock( &gControlDeviceStateLock, oldIrql );

    KeAcquireSpinLock( &gOutputBufferLock, &oldIrql );

    while (!IsListEmpty( &gOutputBufferList )) {

        pList = RemoveHeadList( &gOutputBufferList );

        KeReleaseSpinLock( &gOutputBufferLock, oldIrql );

        pRecordList = CONTAINING_RECORD( pList, RECORD_LIST, List );

        SpyFreeRecord( pRecordList );

        KeAcquireSpinLock( &gOutputBufferLock, &oldIrql );
    }

    KeReleaseSpinLock( &gOutputBufferLock, oldIrql );

    SpyNameDeleteAllNames();

    //
    // All the cleanup is done, so set the gControlDeviceState
    // to CLOSED.
    //

    KeAcquireSpinLock( &gControlDeviceStateLock, &oldIrql );
    gControlDeviceState = CLOSED;
    KeReleaseSpinLock( &gControlDeviceStateLock, oldIrql );
}

////////////////////////////////////////////////////////////////////////
//                                                                    //
//               Device name tracking helper routines                 //
//                                                                    //
////////////////////////////////////////////////////////////////////////

VOID
SpyGetObjectName (
    IN PVOID Object,
    IN OUT PUNICODE_STRING Name
    )
/*++

Routine Description:

    This routine will return the name of the given object.
    If a name can not be found an empty string will be returned.

Arguments:

    Object - The object whose name we want

    Name - A unicode string that is already initialized with a buffer

Return Value:

    None

--*/
{
    NTSTATUS status;
    CHAR nibuf[512];        //buffer that receives NAME information and name
    POBJECT_NAME_INFORMATION nameInfo = (POBJECT_NAME_INFORMATION)nibuf;
    ULONG retLength;

    PAGED_CODE();

    status = ObQueryNameString( Object, 
                                nameInfo, 
                                sizeof(nibuf), 
                                &retLength );

    //
    //  Init current length, if we have an error a NULL string will be returned
    //

    Name->Length = 0;

    if (NT_SUCCESS( status )) {

        //
        //  Copy what we can of the name string
        //

        RtlCopyUnicodeString( Name, &nameInfo->Name );
    }
}

//
//  VERSION NOTE:
//
//  This helper routine is only needed when enumerating all volumes in the
//  system, which is only supported on Windows XP and later.
//

#if WINVER >= 0x0501

VOID
SpyGetBaseDeviceObjectName (
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PUNICODE_STRING Name
    )
/*++

Routine Description:

    This locates the base device object in the given attachment chain and then
    returns the name of that object.

    If no name can be found, an empty string is returned.

Arguments:

    Object - The object whose name we want

    Name - A unicode string that is already initialized with a buffer

Return Value:

    None

--*/
{
    PAGED_CODE();
    
    //
    //  Get the base file system device object
    //

    ASSERT( NULL != gSpyDynamicFunctions.GetDeviceAttachmentBaseRef );
    DeviceObject = (gSpyDynamicFunctions.GetDeviceAttachmentBaseRef)( DeviceObject );

    //
    //  Get the name of that object
    //

    SpyGetObjectName( DeviceObject, Name );

    //
    //  Remove the reference added by IoGetDeviceAttachmentBaseRef
    //

    ObDereferenceObject( DeviceObject );
}

#endif

BOOLEAN
SpyFindSubString (
    IN PUNICODE_STRING String,
    IN PUNICODE_STRING SubString
    )
/*++

Routine Description:
    This routine looks to see if SubString is a substring of String.

Arguments:
    String - the string to search in
    SubString - the substring to find in String

Return Value:
    Returns TRUE if the substring is found in string and FALSE otherwise.
    
--*/
{
    ULONG index;

    //
    //  First, check to see if the strings are equal.
    //

    if (RtlEqualUnicodeString( String, SubString, TRUE )) {

        return TRUE;
    }

    //
    //  String and SubString aren't equal, so now see if SubString
    //  is in String any where.
    //

    for (index = 0;
         index + SubString->Length <= String->Length;
         index++) {

        if (_wcsnicmp( &String->Buffer[index], SubString->Buffer, SubString->Length ) == 0) {

            //
            //  SubString is found in String, so return TRUE.
            //
            
            return TRUE;
        }
    }

    return FALSE;
}

VOID
SpyStoreUserName (
    IN PFILESPY_DEVICE_EXTENSION devExt,
    IN PUNICODE_STRING UserName
    )
/*++

Routine Description:

    Stores the current device name in the device extension.  If
    this name is already in the device name list of this extension,
    it will not be added.  If there is already a name for this device, 
    the new device name is appended to the DeviceName in the device extension.
    
Arguments:

    devExt - The device extension that will store the
        device name.

    UserName - The device name as specified by the user to be stored.

Return Value:

    None

--*/
{
    //
    //  See if this UserName is already in the list of user names filespy
    //  keeps in its device extension.  If not, add it to the list.
    //

    if (!SpyFindSubString( &devExt->UserNames, UserName )) {

        //
        //  We didn't find this name in the list, so if there are no names 
        //  in the UserNames list, just append UserName.  Otherwise, append a
        //  delimiter then append UserName.
        //

        if (devExt->UserNames.Length == 0) {

            RtlAppendUnicodeStringToString( &devExt->UserNames, UserName );

        } else {

            RtlAppendUnicodeToString( &devExt->UserNames, L", " );
            RtlAppendUnicodeStringToString( &devExt->UserNames, UserName );
        }
    }

    //
    //  See if this UserName is already in the list of device names filespy
    //  keeps in its device extension.  If not, add it to the list.
    //

    if (!SpyFindSubString( &devExt->DeviceName, UserName )) {

        //
        //  We didn't find this name in the list, so if there are no names 
        //  in the UserNames list, just append UserName.  Otherwise, append a
        //  delimiter then append UserName.
        //

        if (devExt->DeviceName.Length == 0) {

            RtlAppendUnicodeStringToString( &devExt->DeviceName, UserName );

        } else {

            RtlAppendUnicodeToString( &devExt->DeviceName, L", " );
            RtlAppendUnicodeStringToString( &devExt->DeviceName, UserName );
        }
    }
}

////////////////////////////////////////////////////////////////////////
//                                                                    //
//                        Debug support routines                      //
//                                                                    //
////////////////////////////////////////////////////////////////////////

VOID
SpyDumpIrpOperation (
    IN BOOLEAN InOriginatingPath,
    IN PIRP Irp
    )
/*++

Routine Description:

    This routine is for debugging and prints out a string to the
    debugger specifying what Irp operation is being seen.
    
Arguments:

    InOriginatingPath - TRUE if we are in the originating path
        for the IRP, FALSE if in the completion path.

    Irp - The IRP for this operation.
        
Return Value:

    None.
    
--*/
{
    CHAR irpMajorString[OPERATION_NAME_BUFFER_SIZE];
    CHAR irpMinorString[OPERATION_NAME_BUFFER_SIZE];
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation( Irp );

    GetIrpName(irpSp->MajorFunction,
               irpSp->MinorFunction,
               irpSp->Parameters.FileSystemControl.FsControlCode,
               irpMajorString,irpMinorString);


    if (InOriginatingPath) {

        DbgPrint( "FILESPY: Irp preoperation for %s %s\n", irpMajorString, irpMinorString );
            
    } else {
    
        DbgPrint( "FILESPY: Irp postoperation for %s %s\n", irpMajorString, irpMinorString );
    }
}

VOID
SpyDumpFastIoOperation (
    IN BOOLEAN InPreOperation,
    IN FASTIO_TYPE FastIoOperation
    )
/*++

Routine Description:

    This routine is for debugging and prints out a string to the
    debugger specifying what FsFilter operation is being seen.
    
Arguments:

    InPreOperation - TRUE if we have not called down to the next
        device in the stack, FALSE otherwise.

    FastIoOperation - The code for the Fast Io operation.
    
Return Value:

    None.
    
--*/
{
    CHAR operationString[OPERATION_NAME_BUFFER_SIZE];

    GetFastioName(FastIoOperation,
               operationString);


    if (InPreOperation) {
    
        DbgPrint( "FILESPY: Fast IO preOperation for %s\n", operationString );

    } else {

        DbgPrint( "FILESPY: Fast IO postOperation for %s\n", operationString );
    }
}

#if WINVER >= 0x0501 /* See comment in DriverEntry */

VOID
SpyDumpFsFilterOperation (
    IN BOOLEAN InPreOperationCallback,
    IN PFS_FILTER_CALLBACK_DATA Data
    )
/*++

Routine Description:

    This routine is for debugging and prints out a string to the
    debugger specifying what FsFilter operation is being seen.
    
Arguments:

    InPreOperationCallback - TRUE if we are in a preOperation 
        callback, FALSE otherwise.

    Data - The FS_FILTER_CALLBACK_DATA structure for this
        operation.
        
Return Value:

    None.
    
--*/
{
    CHAR operationString[OPERATION_NAME_BUFFER_SIZE];


    GetFsFilterOperationName(Data->Operation,operationString);

    if (InPreOperationCallback) {
    
        DbgPrint( "FILESPY: FsFilter preOperation for %s\n", operationString );

    } else {

        DbgPrint( "FILESPY: FsFilter postOperation for %s\n", operationString );
    }
}

#endif

