www.pudn.com > Ó²ÅÌµÄ¼à¿Ø.rar > fspyLib.c


/*++ 
 
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. 
 
Author: 
 
Environment: 
 
    Kernel mode 
 
 
Revision History: 
 
--*/ 
 
#include  
 
#include  
#include "filespy.h" 
#include "fspyKern.h" 
 
// 
//  NameLookup Flags 
// 
//    These are flags passed to the name lookup routine to identify different 
//    ways the name of a file can be obtained 
// 
#define NAMELOOKUPFL_CAN_GET_NAME_FROM_FILEOBJ  0x00000001 
                // if set, the filename can be retrieved from the file object 
 
#define NAMELOOKUPFL_OPEN_BY_ID                 0x00000002 
                // if set and we are looking up the name in the file object, 
                // the file object does not actually contain a name but it 
                // contains a file/object ID. 
 
 
//////////////////////////////////////////////////////////////////////// 
//                                                                    // 
//                  Memory allocation routines                        // 
//                                                                    // 
//////////////////////////////////////////////////////////////////////// 
 
DBGSTATIC 
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 suceeded 
        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, MSFM_TAG ); 
#else 
 
    // 
    //  When we are not debugging the memory usage, we use a lookaside 
    //  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 
                newRecordType = RECORD_TYPE_STATIC | 
                                RECORD_TYPE_EXCEED_MEMORY_ALLOWANCE; 
#ifdef MEMORY_DBG 
                ExFreePool( newBuffer ); 
#else 
                ExFreeToNPagedLookasideList( &gFreeBufferList, newBuffer ); 
#endif 
                newBuffer = NULL; 
            } 
        } 
    }  else { 
        newRecordType = RECORD_TYPE_STATIC | 
                        RECORD_TYPE_OUT_OF_MEMORY; 
    } 
 
    if (RecordType) { 
        *RecordType = newRecordType; 
    } 
 
    return newBuffer; 
} 
 
DBGSTATIC 
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 
    ExFreePool( Buffer ); 
#else 
    ExFreeToNPagedLookasideList( &gFreeBufferList, Buffer ); 
#endif 
 
    // 
    // Update the count 
    // 
    if (Counter) { 
        InterlockedDecrement(Counter); 
    } 
} 
 
 
/*********************************************** 
    Logging routines 
***********************************************/ 
 
DBGSTATIC 
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; 
	ULONG T = 1;   
 
    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( (PVOID)&gStaticBufferInUse, &T, 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 = sizeof(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 = sizeof(LOG_RECORD); 
        newRecord->LogRecord.SequenceNumber = currentSequenceNumber; 
    } 
    KeReleaseSpinLock(&gLogSequenceLock, irql); 
 
    return( newRecord ); 
} 
 
DBGSTATIC 
VOID 
SpyFreeRecord ( 
    IN PRECORD_LIST Record 
    ) 
/*++ 
 
Routine Description: 
 
    Frees a RECORD_LIST, which returns the memory to the gFreeBufferList lookaside 
    list and updates the gRecordsAllocated count. 
 
Arguments: 
 
    Record - the record to free 
 
Return Value: 
 
    None. 
 
--*/ 
{ 
    if (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 ); 
    } 
} 
 
//////////////////////////////////////////////////////////////////////// 
//                                                                    // 
//                       Logging routines                             // 
//                                                                    // 
//////////////////////////////////////////////////////////////////////// 
 
DBGSTATIC 
VOID 
SpyLogIrp ( 
    IN  PIRP         Irp, 
    IN  UCHAR        LoggingFlags, 
    OUT PRECORD_LIST RecordList 
    ) 
/*++ 
 
Routine Description: 
 
    Records the Irp necessary information according to LoggingFlags in 
    RecordList.  For any activity on the Irp path of a device being 
    logged, this function should get called twice: once on the Irp's 
    originating path and once on the Irp's completion path. 
 
Arguments: 
 
    Irp - The Irp that contains the information we want to record. 
    LoggingFlags - The flags that say what to log. 
    RecordList - The PRECORD_LIST in which the Irp information is stored. 
 
Return Value: 
 
    None. 
 
--*/ 
{ 
    PIO_STACK_LOCATION pIrpStack; 
    PRECORD_IRP        pRecordIrp; 
    PDEVICE_EXTENSION  deviceExtension; 
    PUNICODE_STRING    volumeName; 
    ULONG              lookupFlags; 
 
    pRecordIrp = &RecordList->LogRecord.Record.RecordIrp; 
 
    pIrpStack = IoGetCurrentIrpStackLocation(Irp); 
 
    if (LoggingFlags & LOG_ORIGINATING_IRP) { 
        // 
        // 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. 
        // 
        RecordList->LogRecord.RecordType |= RECORD_TYPE_IRP; 
 
 
        pRecordIrp->IrpMajor        = pIrpStack->MajorFunction; 
        pRecordIrp->IrpMinor        = pIrpStack->MinorFunction; 
        pRecordIrp->IrpFlags        = Irp->Flags; 
        pRecordIrp->FileObject      = (FILE_ID)pIrpStack->FileObject; 
        pRecordIrp->ProcessId       = (FILE_ID)PsGetCurrentProcessId(); 
        pRecordIrp->ThreadId        = (FILE_ID)PsGetCurrentThreadId(); 
 
        KeQuerySystemTime(&(pRecordIrp->OriginatingTime)); 
 
        // 
        //  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. 
        // 
 
        deviceExtension = pIrpStack->DeviceObject->DeviceExtension; 
 
        if (deviceExtension && 
            (deviceExtension->NextDriverDeviceObject->DeviceType == 
                FILE_DEVICE_DISK_FILE_SYSTEM)) { 
                volumeName = &(deviceExtension->DeviceName); 
        } else { 
            volumeName = NULL; 
        } 
 
        lookupFlags = 0; 
        if (pIrpStack->MajorFunction == IRP_MJ_CREATE) { 
            lookupFlags |= NAMELOOKUPFL_CAN_GET_NAME_FROM_FILEOBJ; 
            if (pIrpStack->Parameters.Create.Options & FILE_OPEN_BY_FILE_ID) { 
                lookupFlags |= NAMELOOKUPFL_OPEN_BY_ID; 
            } 
        } 
 
        SpyNameLookup( 
            RecordList, 
            pIrpStack->FileObject, 
            lookupFlags, 
            volumeName); 
    } 
 
    if (LoggingFlags & LOG_COMPLETION_IRP) { 
        // 
        // Record the information we use for a completion Irp. 
        // 
 
        pRecordIrp->ReturnStatus   = Irp->IoStatus.Status; 
        pRecordIrp->ReturnInformation = Irp->IoStatus.Information; 
        KeQuerySystemTime(&(pRecordIrp->CompletionTime)); 
    } 
} 
 
DBGSTATIC 
PRECORD_LIST 
SpyLogFastIoStart ( 
    IN  FASTIO_TYPE    FastIoType, 
    IN  UCHAR          LoggingFlags, 
    IN  PFILE_OBJECT   FileObject, 
    IN  PLARGE_INTEGER FileOffset, 
    IN  ULONG          Length, 
    IN  BOOLEAN        Wait 
    ) 
/*++ 
 
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) 
    LoggingFlags - The flags that say what to log. (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) 
    ControlCode - The I/O control value for this operation (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; 
    PUNICODE_STRING volumeName; 
 
    // 
    // 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 
    // 
    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->FileOffset.QuadPart = ((FileOffset != NULL) ? 
                                                FileOffset->QuadPart : 
                                                0); 
    pRecordFastIo->Length = Length; 
    pRecordFastIo->Wait = Wait; 
    pRecordFastIo->Reserved = 0; 
 
    if (FileObject && 
        FileObject->DeviceObject && 
        FileObject->DeviceObject->DeviceExtension) { 
        volumeName = 
            &((PDEVICE_EXTENSION) 
              (FileObject->DeviceObject->DeviceExtension))->DeviceName ; 
    } else { 
        volumeName = NULL; 
    } 
 
    SpyNameLookup(pRecordList, FileObject, 0, volumeName); 
 
    return pRecordList; 
} 
 
DBGSTATIC 
VOID 
SpyLogFastIoComplete ( 
    IN  UCHAR            LoggingFlags, 
    IN  PFILE_OBJECT     FileObject, 
    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: 
    LoggingFlags - The flags that say what to log. 
    FileObject - Pointer to the file object this operation is on (OPTIONAL) 
    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); 
} 
 
DBGSTATIC 
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; 
 
    ExAcquireSpinLock( &gControlDeviceStateLock, &controlDeviceIrql ); 
    if (gControlDeviceState == OPENED) { 
        // 
        // The device is still open so add this record onto the list 
        // 
        ExAcquireSpinLock(&gOutputBufferLock, &outputBufferIrql); 
        InsertTailList(&gOutputBufferList, &NewRecord->List); 
        ExReleaseSpinLock(&gOutputBufferLock, outputBufferIrql); 
    } else { 
        // 
        // We can no longer log this record, so free the record 
        // 
        SpyFreeRecord( NewRecord ); 
    } 
    ExReleaseSpinLock( &gControlDeviceStateLock, controlDeviceIrql ); 
 
    return STATUS_SUCCESS; 
} 
 
//////////////////////////////////////////////////////////////////////// 
//                                                                    // 
//                    FileName cache routines                         // 
//                                                                    // 
//////////////////////////////////////////////////////////////////////// 
 
DBGSTATIC 
PHASH_ENTRY 
SpyHashBucketLookup ( 
    IN PLIST_ENTRY  ListHead, 
    IN PFILE_OBJECT FileObject 
    ) 
/*++ 
 
Routine Description: 
 
    This routine looks up the FileObject in the give hash bucket.  This routine 
    does NOT lock the hash bucket. 
 
Arguments: 
 
    ListHead - hash list to search 
    FileObject - the FileObject to look up. 
 
Return Value: 
 
    A pointer to the hash table entry.  NULL if not found 
 
--*/ 
{ 
    PHASH_ENTRY pHash; 
    PLIST_ENTRY pList; 
 
    pList = ListHead->Flink; 
 
    while(pList != ListHead){ 
        pHash = CONTAINING_RECORD( pList, HASH_ENTRY, List ); 
 
        if (FileObject == pHash->FileObject) { 
            return pHash; 
        } 
 
        pList = pList->Flink; 
    } 
 
    return NULL; 
} 
 
DBGSTATIC 
USHORT 
SpyNameLookup ( 
    IN PRECORD_LIST    RecordList, 
    IN PFILE_OBJECT    FileObject, 
    IN ULONG           LookupFlags, 
    IN PUNICODE_STRING VolumeName 
    ) 
/*++ 
 
Routine Description: 
 
    This routine looks up the FileObject in the hash table.  If the FileObject 
    is found in the hash table, copy the associated file name to RecordList. 
    Otherwise, if LookupFlags has NAMELOOKUPFL_CAN_GET_NAME_FROM_FILEOBJ set: 
 
        1) copy the name out of the file object (or related file object chain) 
           into a newly created hash table entry 
 
        2) insert it into the hash table 
 
    If NAMELOOKUPFL_CAN_GET_NAME_FROM_FILEOBJ is not set, then return 0 if 
    there is no hash table hit. 
 
Arguments: 
 
    RecordList - RecordList to copy name to. 
    FileObject - the FileObject to look up. 
    LookInFileObject - see routine description 
    VolumeName - Drive letter to prepend to the path (eg c:) 
 
Return Value: 
 
    The number of bytes copied. 
 
--*/ 
{ 
    UINT_PTR        hashIndex; 
    KIRQL           oldIrql; 
    PHASH_ENTRY     pHash; 
    PLIST_ENTRY     pList; 
    PHASH_ENTRY     newHash; 
    PLIST_ENTRY     listHead; 
    PUNICODE_STRING newName; 
    PCHAR           buffer; 
    SHORT           length; 
 
    if (FileObject == NULL) { 
        return 0; 
    } 
 
    hashIndex = HASH_FUNC(FileObject); 
 
    gHashStat.Lookups++; 
 
    KeAcquireSpinLock(&gHashLockTable[hashIndex], &oldIrql); 
 
    listHead = &gHashTable[hashIndex]; 
 
    pHash = SpyHashBucketLookup(&gHashTable[hashIndex], FileObject); 
 
    if (pHash) { 
 
        length = MINIMUM( 
            MAX_NAME_SPACE, 
            pHash->Name.Length + sizeof(WCHAR) // want the terminating NULL 
        ); 
 
        ASSERT((length > 0) && (length <= MAX_NAME_SPACE)); 
 
        RtlCopyMemory(RecordList->LogRecord.Name, pHash->Name.Buffer, length); 
 
        RecordList->LogRecord.Length += length; 
 
        KeReleaseSpinLock(&gHashLockTable[hashIndex], oldIrql); 
 
        gHashStat.LookupHits++; 
 
        return length; 
    } 
 
    KeReleaseSpinLock(&gHashLockTable[hashIndex], oldIrql); 
 
    if (!(LookupFlags & NAMELOOKUPFL_CAN_GET_NAME_FROM_FILEOBJ)) { 
        return 0; 
    } 
 
    // 
    // If it is not in the table, add it. 
    // 
 
    buffer = SpyAllocateBuffer(&gNamesAllocated, gMaxNamesToAllocate, NULL); 
 
    if(buffer){ 
        newHash = (PHASH_ENTRY) buffer; 
        newName = &newHash->Name; 
        newName->MaximumLength = RECORD_SIZE - sizeof(HASH_ENTRY); 
        newName->Buffer = (PWCHAR) (buffer + sizeof(HASH_ENTRY)); 
        newName->Length = SpyGetFullPathName( 
                                FileObject, 
                                (PCHAR) newName->Buffer, 
                                newName->MaximumLength, 
                                VolumeName, 
                                LookupFlags); 
 
        if (newName->Length) { 
            newHash->FileObject = FileObject; 
            KeAcquireSpinLock(&gHashLockTable[hashIndex], &oldIrql); 
 
            // 
            // search again 
            // 
            pHash = SpyHashBucketLookup(&gHashTable[hashIndex], FileObject); 
 
            if (pHash) { 
                length = MINIMUM( 
                    MAX_NAME_SPACE, 
                    pHash->Name.Length + sizeof(WCHAR) // we want the 
                                                       // terminating NULL 
                    ); 
 
                ASSERT(length > 0 && length <= MAX_NAME_SPACE); 
 
                RtlCopyMemory( 
                    RecordList->LogRecord.Name, 
                    pHash->Name.Buffer, 
                    length); 
 
                RecordList->LogRecord.Length += length; 
 
                KeReleaseSpinLock(&gHashLockTable[hashIndex], oldIrql); 
 
                SpyFreeBuffer(buffer, &gNamesAllocated); 
 
                return length; 
            } 
 
            // 
            // It wasn't found, add the new entry 
            // 
 
            length = MINIMUM( 
                MAX_NAME_SPACE, 
                newHash->Name.Length 
                ); 
 
            ASSERT(length > 0 && length <= MAX_NAME_SPACE); 
 
            RtlCopyMemory( 
                RecordList->LogRecord.Name, 
                newHash->Name.Buffer, 
                length); 
 
            RecordList->LogRecord.Length += length; 
 
            InsertHeadList(listHead, &newHash->List); 
 
            gHashCurrentCounters[hashIndex]++; 
 
            if (gHashCurrentCounters[hashIndex] > gHashMaxCounters[hashIndex]) { 
                gHashMaxCounters[hashIndex] = gHashCurrentCounters[hashIndex]; 
            } 
 
            KeReleaseSpinLock(&gHashLockTable[hashIndex], oldIrql); 
 
            return length; 
 
        } else { 
            SpyFreeBuffer (buffer, &gNamesAllocated); 
        } 
    } 
 
    return 0; 
} 
 
DBGSTATIC 
VOID 
SpyNameDeleteAllNames ( 
    VOID 
    ) 
/*++ 
 
Routine Description: 
 
    This will free all entries from the hash table 
 
Arguments: 
 
    None 
 
Return Value: 
 
    None 
 
 
--*/ 
{ 
    KIRQL       oldIrql; 
    PHASH_ENTRY pHash; 
    PLIST_ENTRY pList; 
    int i; 
 
    for (i=0;i < HASH_SIZE;i++) { 
 
        KeAcquireSpinLock(&gHashLockTable[i], &oldIrql); 
 
        while (!IsListEmpty(&gHashTable[i])) { 
            pList = RemoveHeadList(&gHashTable[i]); 
            pHash = CONTAINING_RECORD( pList, HASH_ENTRY, List ); 
            SpyFreeBuffer( pHash, &gNamesAllocated); 
        } 
 
        gHashCurrentCounters[i] = 0; 
 
        KeReleaseSpinLock(&gHashLockTable[i], oldIrql); 
    } 
} 
 
DBGSTATIC 
VOID 
SpyNameDelete ( 
    IN PFILE_OBJECT FileObject 
    ) 
/*++ 
 
Routine Description: 
 
    This routine looks up the FileObject in the hash table.  If it is found, 
    it deletes it and frees the memory. 
 
Arguments: 
 
    FileObject - the FileObject to look up. 
 
Return Value: 
 
    None 
 
 
--*/ 
{ 
    UINT_PTR    hashIndex; 
    KIRQL       oldIrql; 
    PHASH_ENTRY pHash; 
    PLIST_ENTRY pList; 
    PLIST_ENTRY listHead; 
 
    hashIndex = HASH_FUNC(FileObject); 
 
    gHashStat.DeleteLookups++; 
 
    KeAcquireSpinLock(&gHashLockTable[hashIndex], &oldIrql); 
 
    listHead = &gHashTable[hashIndex]; 
 
    pList = listHead->Flink; 
 
    while(pList != listHead){ 
        pHash = CONTAINING_RECORD( pList, HASH_ENTRY, List ); 
 
        if (FileObject == pHash->FileObject) { 
            gHashStat.DeleteLookupHits++; 
            gHashCurrentCounters[hashIndex]--; 
            RemoveEntryList(pList); 
            SpyFreeBuffer( pHash, &gNamesAllocated ); 
            break; 
        } 
 
        pList = pList->Flink; 
    } 
 
    KeReleaseSpinLock(&gHashLockTable[hashIndex], oldIrql); 
} 
 
DBGSTATIC 
USHORT 
SpyGetFullPathName ( 
    IN PFILE_OBJECT    FileObject, 
    IN PCHAR           FName, 
    IN USHORT          MaxLength, 
    IN PUNICODE_STRING VolumeName, 
    IN ULONG           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. 
    This returns the length of the name returned, this length includes the 
    trailing NULL character. 
 
Arguments: 
 
    FileObject - Pointer to the FileObject to the get name of. 
 
    FileName - Pointer to a buffer to put the name in.  It is assumed that 
    the caller allocates and frees the memory used by the string. 
 
    MaxLength - Length of FileName buffer in bytes. 
 
Return Value: 
 
    The number of bytes copied into the callers buffer. Zero indicates an 
    error. 
 
 
--*/ 
{ 
    PFILE_OBJECT pRelatedFileObject; 
    PWSTR        origNameBuffer = (PWSTR)FName; 
    PWSTR        wFileName; 
    USHORT       length; 
    USHORT       volumeNameLength = 0; 
    USHORT       i; 
 
    if (FileObject == NULL) { 
        return 0; 
    } 
 
    // 
    // Names buffers in file objects might be paged. 
    // 
 
    if (KeGetCurrentIrql() >= DISPATCH_LEVEL) { 
        return 0; 
    } 
 
    if (VolumeName) { 
        volumeNameLength = VolumeName->Length; 
    } 
 
    // 
    // This is an operation on the volume 
    // 
 
    if (FileObject->FileName.Length == 0) { 
 
        length = MINIMUM(MaxLength, 
                    (gVolumeString.MaximumLength + volumeNameLength)); 
 
        if (volumeNameLength) { 
            RtlCopyMemory(origNameBuffer,VolumeName->Buffer,volumeNameLength); 
        } 
 
        RtlCopyMemory( 
            &origNameBuffer[volumeNameLength/sizeof(WCHAR)], 
            gVolumeString.Buffer, 
            length ); 
 
        return length; 
    } 
 
    // 
    // Check for "Open by ID" and generate the names if we are 
    // 
    if (LookupFlags & NAMELOOKUPFL_OPEN_BY_ID) { 
        UNICODE_STRING uIdName; 
        ANSI_STRING aIdName; 
        char ansiBuffer[64];   // buffer to build string names into 
        PUCHAR idbuf; 
#       define OBJECT_ID_KEY_LENGTH 16 
 
        length = 0;         // set default return length 
 
        if (volumeNameLength) {     // set volume name if we have one 
            RtlCopyMemory(origNameBuffer,VolumeName->Buffer,volumeNameLength); 
 
            origNameBuffer += (volumeNameLength/sizeof(WCHAR)); 
            length += volumeNameLength; 
        } 
 
 
        if (FileObject->FileName.Length == sizeof(LONGLONG)) { 
            // opening by FILE ID, generate a name 
            PLONGLONG fileref; 
 
            fileref = (PLONGLONG) FileObject->FileName.Buffer; 
 
            sprintf( ansiBuffer, "<%016I64x>", *fileref ); 
 
        } else if ((FileObject->FileName.Length == OBJECT_ID_KEY_LENGTH) || 
                   (FileObject->FileName.Length == OBJECT_ID_KEY_LENGTH + sizeof(WCHAR))) { 
            // opening by Object ID, generate a name 
 
            idbuf = (PUCHAR)&FileObject->FileName.Buffer[0]; 
            if (FileObject->FileName.Length != OBJECT_ID_KEY_LENGTH) { 
                // skip win32k backslash at start of buffer 
                idbuf = (PUCHAR)&FileObject->FileName.Buffer[1]; 
            } 
 
            sprintf(ansiBuffer,"<%08x-%04hx-%04hx-%04hx-%04hx%08x>", 
                        *(PULONG)&idbuf[0], 
                        *(PUSHORT)&idbuf[0+4], 
                        *(PUSHORT)&idbuf[0+4+2], 
                        *(PUSHORT)&idbuf[0+4+2+2], 
                        *(PUSHORT)&idbuf[0+4+2+2+2], 
                        *(PULONG)&idbuf[0+4+2+2+2+2]); 
 
        } else { 
            // unknown ID format 
            sprintf(ansiBuffer,"",FileObject->FileName.Length); 
        } 
 
        // 
        // Convert the "sprintf" string to unicode 
        // 
 
        aIdName.Length = strlen(ansiBuffer); 
        aIdName.MaximumLength = aIdName.Length+1; 
        aIdName.Buffer = ansiBuffer; 
 
        uIdName.Length = 0; 
        uIdName.MaximumLength = (MaxLength - (USHORT)length); 
        uIdName.Buffer = origNameBuffer; 
        RtlAnsiStringToUnicodeString(&uIdName,&aIdName,FALSE); 
 
        return (USHORT)(uIdName.Length + length + sizeof(WCHAR)); 
    } else if (FileObject->RelatedFileObject) { 
        // 
        // must be a relative open 
        // 
 
        // Since we have the leaf name, we are going to  have to generate 
        // the name backwards.  Start filling in at the end  of the buffer 
        // and when we are done, copy it to the front of the buffer. 
        // 
        // 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.  Given that, incomplete pathnames are possible. 
        // 
        pRelatedFileObject = FileObject; 
        wFileName = &origNameBuffer[MaxLength/sizeof(WCHAR)]; 
 
        *--wFileName = UNICODE_NULL;      // null terminate the string 
 
        // 
        // Only look at the name in FileObject and FileObject->RelatedFileObject. 
        // 
        for (i = 0; i < 2 && pRelatedFileObject; i++, pRelatedFileObject = pRelatedFileObject->RelatedFileObject) { 
            // add a seperator if we need one.  Don't add one before the 
            // first object and don't add one if this is a "data stream" 
            // or if there is already a seperator. 
            if ( (pRelatedFileObject != FileObject) && 
                 (wFileName > origNameBuffer) && 
                 (*wFileName != L'\\') &&  // seperator at front of last name? 
                 (*wFileName != L':') &&   // a data stream? 
 
                                           // seperator at end of next name? 
                 (pRelatedFileObject->FileName.Buffer != NULL) && 
                 (pRelatedFileObject->FileName.Length > 0) && 
                 (pRelatedFileObject->FileName.Buffer[ 
                            (pRelatedFileObject->FileName.Length/ 
                            sizeof(WCHAR))-1] != L'\\')) { 
                *--wFileName = L'\\'; 
            } 
 
            length = pRelatedFileObject->FileName.Length / sizeof(WCHAR); 
            if (wFileName-length < origNameBuffer) { 
                                            // includes NULL character 
                length = MINIMUM(MaxLength, gOverrunString.MaximumLength); 
                RtlCopyMemory( origNameBuffer, gOverrunString.Buffer, length ); 
                return length; 
            } 
 
            wFileName -= length; 
            ASSERT(wFileName >= origNameBuffer); 
 
            RtlCopyMemory( wFileName, 
                    pRelatedFileObject->FileName.Buffer, 
                    pRelatedFileObject->FileName.Length); 
 
        } 
         
        // 
        // If this is not a full path name, prepend "...\" to it. 
        // 
         
        if (*wFileName != L'\\') { 
            PWSTR relativePrefix = L"...\\"; 
            ULONG prefixLength; 
 
            prefixLength = wcslen(relativePrefix);      // length in WCHARs 
 
            if (wFileName-prefixLength >= origNameBuffer) { 
                wFileName -= prefixLength; 
                RtlCopyMemory(wFileName, relativePrefix, prefixLength * sizeof(WCHAR));  
            }            
        } 
         
        if ((volumeNameLength > 0) && 
                    ((wFileName-(volumeNameLength/sizeof(WCHAR))) >= 
                    origNameBuffer)) { 
            wFileName -= (volumeNameLength / sizeof(WCHAR)); 
 
            RtlCopyMemory( 
                wFileName, 
                VolumeName->Buffer, 
                volumeNameLength ); 
        } 
 
        // move string to FRONT of buffer 
        length = (USHORT)((&origNameBuffer[MaxLength/sizeof(WCHAR)] - wFileName) * 
                        sizeof(WCHAR)); 
        RtlMoveMemory(origNameBuffer,wFileName,length); 
        return length;  // including NULL 
    } else { 
        // absolute path 
 
        if ((volumeNameLength + 
                FileObject->FileName.Length + 
                sizeof(WCHAR)) <= MaxLength) { 
            if (volumeNameLength) { 
                RtlCopyMemory( 
                    origNameBuffer, 
                    VolumeName->Buffer, 
                    volumeNameLength ); 
            } 
 
            RtlCopyMemory( 
                &origNameBuffer[volumeNameLength/sizeof(WCHAR)], 
                FileObject->FileName.Buffer, 
                FileObject->FileName.Length); 
 
            // null terminate the name 
            origNameBuffer[(volumeNameLength + FileObject->FileName.Length)/ 
                    sizeof(WCHAR)] = 0; 
 
            // return length including NULL 
            return ( 
                volumeNameLength + 
                FileObject->FileName.Length + 
                sizeof(WCHAR)); 
        } else { 
            length = MINIMUM(MaxLength, gOverrunString.MaximumLength); 
            RtlCopyMemory( origNameBuffer, gOverrunString.Buffer, length ); 
            return length; 
        } 
    } 
} 
 
 
 
////////////////////////////////////////////////////////////////////////// 
//                                                                      // 
//                     Library support routines                         // 
//                                                                      // 
////////////////////////////////////////////////////////////////////////// 
 
DBGSTATIC 
VOID 
SpyReadDriverParameters ( 
    IN     PUNICODE_STRING  RegistryPath, 
    IN     PDRIVER_OBJECT   DriverObject 
    ) 
/*++ 
 
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; 
    ULONG                          keyIndex; 
    UNICODE_STRING                 valueName; 
    PKEY_VALUE_PARTIAL_INFORMATION pValuePartialInfo; 
    PWSTR                          attachDrives; 
 
    InitializeObjectAttributes( 
        &attributes, 
        RegistryPath, 
        OBJ_CASE_INSENSITIVE, 
        NULL, 
        NULL); 
 
    status = ZwOpenKey( 
        &driverRegKey, 
        KEY_READ, 
        &attributes); 
 
    if (!NT_SUCCESS(status)) { 
        driverRegKey = NULL; 
        goto SpyReadDriverParameters_Error; 
    } 
 
    bufferSize = sizeof(KEY_VALUE_PARTIAL_INFORMATION) + ATTACH_BUFFER_SIZE; 
    buffer = ExAllocatePool(NonPagedPool, bufferSize); 
 
    if (NULL == buffer) { 
        goto SpyReadDriverParameters_Error; 
    } 
 
    // 
    // 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)); 
    } else { 
        gMaxRecordsToAllocate = DEFAULT_MAX_RECORDS_TO_ALLOCATE; 
    } 
 
    // 
    // 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)); 
    } else { 
        gMaxNamesToAllocate = DEFAULT_MAX_NAMES_TO_ALLOCATE; 
    } 
 
 
#ifdef SPY_BOOT_DRIVER 
    // 
    // Read initial drives to attach to from the registry 
    // 
 
    RtlInitUnicodeString(&valueName, ATTACH_TO); 
 
    status = ZwQueryValueKey( 
        driverRegKey, 
        &valueName, 
        KeyValuePartialInformation, 
        buffer, 
        bufferSize, 
        &resultLength); 
 
    if (NT_SUCCESS(status)) { 
 
        pValuePartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION) buffer; 
        if(pValuePartialInfo->Type != REG_MULTI_SZ){ 
            goto SpyReadDriverParameters_Exit; 
        } 
 
 
        attachDrives = (PWSTR)&pValuePartialInfo->Data; 
        if(*attachDrives){ 
            // 
            //  Register with the I/O System to be called again once all the 
            //  devices in the system have been enumerated and started. 
            // 
            IoRegisterBootDriverReinitialization(DriverObject, SpyReinitDriver, buffer); 
            // 
            // In this instance, SpyReinitDriver will free buffer when it is done parsing and attaching. 
            // 
            buffer = NULL; 
        } 
    } 
#endif 
 
    goto SpyReadDriverParameters_Exit; 
 
SpyReadDriverParameters_Error: 
    gMaxRecordsToAllocate = DEFAULT_MAX_RECORDS_TO_ALLOCATE; 
    gMaxNamesToAllocate = DEFAULT_MAX_NAMES_TO_ALLOCATE; 
 
SpyReadDriverParameters_Exit: 
    if (NULL != buffer) { 
        ExFreePool(buffer); 
    } 
    if (NULL != driverRegKey) { 
        ZwClose(driverRegKey); 
    } 
 
    return; 
} 
 
BOOLEAN 
SpyFindSubString ( 
    IN PUNICODE_STRING String, 
    IN PUNICODE_STRING SubString 
    ) 
{ 
    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 
    //  in 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; 
} 
 
PDEVICE_EXTENSION 
SpyFindAttachedDevice ( 
    IN PUNICODE_STRING devName 
    ) 
/*++ 
 
Routine Description: 
    This scans the list of devices we have physical attachments too and 
    sees if we already have an attachment to this device. 
 
Arguments: 
    devName - the name of the device to check 
 
Return Value: 
    The DEVICE_EXTENSION object for the given named object, NULL if it was 
    not found. 
 
--*/ 
{ 
    PDEVICE_EXTENSION devext; 
    PLIST_ENTRY       link; 
 
    try { 
        ExAcquireFastMutex( &gSpyDeviceExtensionListLock ); 
 
        for (link = gSpyDeviceExtensionList.Flink; 
             link != &gSpyDeviceExtensionList; 
             link = link->Flink) { 
            devext = CONTAINING_RECORD(link, DEVICE_EXTENSION, NextDevice); 
 
            if (SpyFindSubString(devName,&devext->DeviceName)) 
            { 
                try_return(devext); 
            } 
        } 
        devext = NULL; 
try_exit: NOTHING; 
    } finally { 
        ExReleaseFastMutex( &gSpyDeviceExtensionListLock ); 
        return devext;        // didn't find the name, return 
    } 
} 
 
BOOLEAN 
SpyAlreadyAttached ( 
    IN PDEVICE_OBJECT CandidateDeviceObject, 
    OUT PDEVICE_EXTENSION *DeviceExtension 
    ) 
/*++ 
 
Routine Description: 
    This routine looks through the list of FileSpy device extensions to 
    see if we have attached to CandidateDeviceObject's device. 
 
    This is necessary to work around attaching to network devices.  All 
    network drives are represented by one device object.  If we 
    have more that one network drive, we don't want to attach to this 
    device object more than once (if we attached more than once, we 
    would see all I/O to any network drive as many times as we are 
    attached). 
 
Arguments: 
 
    CandidateDeviceObject - Device object that the FileSpy driver is 
        considering attaching to.  We want to make sure that we haven't 
        already attached to this device object. 
 
    DeviceExtension - Gets set to the device extension already attached 
        to CandidateDeviceObject if we are already attached to 
        CandidateDeviceObject. 
 
Return Value: 
 
    Returns TRUE if we have already attached and FALSE otherwise. 
 
--*/ 
{ 
    PLIST_ENTRY entry; 
    PDEVICE_EXTENSION currentExtension; 
    PDEVICE_OBJECT currentDevice; 
    BOOLEAN found = FALSE; 
 
    *DeviceExtension = NULL; 
 
    ExAcquireFastMutex( &gSpyDeviceExtensionListLock ); 
 
    try { 
 
        // 
        //  Look through our deviceExtension list and see if we have a device 
        //  extension for CandidateDeviceObject already. 
        // 
 
        for (entry = gSpyDeviceExtensionList.Flink; 
             entry != &gSpyDeviceExtensionList; 
             entry = entry->Flink) { 
 
            currentExtension = CONTAINING_RECORD( entry, DEVICE_EXTENSION, NextDevice); 
 
            if (currentExtension->NextDriverDeviceObject == CandidateDeviceObject) { 
 
                // 
                //  We do have a device extension that references this CandidateDeviceObject 
                //  so return TRUE after setting the output parameter DeviceExtension. 
                // 
                *DeviceExtension = currentExtension; 
                try_return( found = TRUE ); 
 
            } else { 
 
                // 
                //  The NextDriverDeviceObject for this extension may be up the 
                //  stack of CandidateDeviceObject.  If so, we don't want to attach 
                //  again.  So we look through CandidateDeviceObject->AttachedDevice 
                //  so that we catch this. 
                // 
 
                for (currentDevice = currentExtension->NextDriverDeviceObject->AttachedDevice; 
                     currentDevice != NULL; 
                     currentDevice = currentDevice->AttachedDevice) { 
 
                    if (currentDevice == CandidateDeviceObject) { 
 
                        // 
                        //  We found a deviceObject in the stack that we are attached to, 
                        //  so return this extension. 
                        // 
 
                        *DeviceExtension = currentExtension; 
                        try_return( found = TRUE ); 
                    } 
                } 
            } 
        } 
 
        try_exit: NOTHING; 
 
    } finally { 
 
        ExReleaseFastMutex( &gSpyDeviceExtensionListLock ); 
    } 
 
    return found; 
} 
 
NTSTATUS 
SpyAttachDevice ( 
    IN PDEVICE_OBJECT DeviceObject, 
    IN PWSTR          DeviceName 
    ) 
/*++ 
 
Routine Description: 
    This will attempt to physically attach to the given device (defined by 
    name).  This checks to see if we have already physically attached to the 
    device, if so then simply flag the device that we should log.  If not, 
    then do the physical attachment and flag the device that we should log. 
 
    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: 
 
    DeviceObject - Device object for FILESPY driver 
 
    DeviceName - Name of device to attach to 
 
Return Value: 
    Error status code 
 
--*/ 
{ 
    WCHAR               nameBuf[DEVICE_NAME_SZ]; 
    UNICODE_STRING      volumeNameUnicodeString; 
    NTSTATUS            status; 
    PDEVICE_OBJECT      nextDriverDeviceObject; 
    PDEVICE_OBJECT      attachedDeviceObject; 
    PFILE_OBJECT        volumeFileObject; 
    HANDLE              fileHandle; 
    OBJECT_ATTRIBUTES   objectAttributes; 
    PDEVICE_EXTENSION   devext; 
    IO_STATUS_BLOCK     openStatus; 
 
 
    volumeNameUnicodeString.MaximumLength = sizeof(nameBuf); 
    volumeNameUnicodeString.Buffer = nameBuf; 
    volumeNameUnicodeString.Length= 0; 
 
 
    RtlAppendUnicodeToString(&volumeNameUnicodeString,DeviceName); 
    devext = SpyFindAttachedDevice(&volumeNameUnicodeString); 
    if (devext != NULL) { 
        devext->LogThisDevice = TRUE; 
        return STATUS_SUCCESS; 
    } 
 
    volumeNameUnicodeString.Length= 0; 
    RtlAppendUnicodeToString(&volumeNameUnicodeString, L"\\DosDevices\\"); 
    RtlAppendUnicodeToString(&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/*|FILE_DIRECTORY_FILE*/, 
                NULL, 
                0); 
    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) 
    nextDriverDeviceObject = IoGetRelatedDeviceObject( volumeFileObject ); 
    if (nextDriverDeviceObject == NULL) 
    { 
        ObDereferenceObject( volumeFileObject ); 
        ZwClose( fileHandle ); 
 
        return status; 
    } 
 
    if (!SpyAlreadyAttached( nextDriverDeviceObject, &devext )) { 
 
        // 
        //  We are not already attached, so attach to this device object. 
        // 
 
        // create a new device object so we can attach it in the call chain 
        status = IoCreateDevice( 
                    DeviceObject->DriverObject, 
                    sizeof(DEVICE_EXTENSION), 
                    NULL, 
                    nextDriverDeviceObject->DeviceType, 
                    0, 
                    FALSE, 
                    &attachedDeviceObject); 
        if ( !NT_SUCCESS(status) ) { 
            ObDereferenceObject( volumeFileObject ); 
            ZwClose( fileHandle ); 
 
            return status; 
        } 
 
        devext = attachedDeviceObject->DeviceExtension; 
        devext->LogThisDevice = TRUE; 
        devext->Type = FILESPY_DEVICE_TYPE; 
        devext->Size = sizeof( DEVICE_EXTENSION ); 
 
        devext->DeviceName.MaximumLength = sizeof(devext->NameBuffer); 
        devext->DeviceName.Buffer = devext->NameBuffer; 
        devext->DeviceName.Length= 0; 
        RtlAppendUnicodeToString(&devext->DeviceName, DeviceName); 
 
        // Add our device object to chain 
        devext->NextDriverDeviceObject = IoAttachDeviceToDeviceStack( 
                attachedDeviceObject, 
                nextDriverDeviceObject ); 
 
        // see if we could not attach 
        if (devext->NextDriverDeviceObject == NULL)  { 
            IoDeleteDevice(attachedDeviceObject); 
            ObDereferenceObject(volumeFileObject); 
            ZwClose(fileHandle); 
 
            return STATUS_UNSUCCESSFUL; 
        } 
 
        ExAcquireFastMutex(&gSpyDeviceExtensionListLock); 
        InsertTailList(&gSpyDeviceExtensionList, &devext->NextDevice); 
        ExReleaseFastMutex(&gSpyDeviceExtensionListLock); 
 
        if (nextDriverDeviceObject->Flags & DO_BUFFERED_IO) { 
            attachedDeviceObject->Flags |= DO_BUFFERED_IO; 
        } 
 
        if (nextDriverDeviceObject->Flags & DO_DIRECT_IO) { 
            attachedDeviceObject->Flags |= DO_DIRECT_IO; 
        } 
 
        attachedDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; 
 
    } else { 
 
        // 
        //  We are already attached, so just add this name to the 
        //  device name for this device extension. 
        // 
 
        RtlAppendUnicodeToString( &devext->DeviceName, L"," ); 
        RtlAppendUnicodeToString( &devext->DeviceName, DeviceName ); 
    } 
 
    ObDereferenceObject( volumeFileObject ); 
    ZwClose( fileHandle ); 
    return STATUS_SUCCESS; 
} 
 
NTSTATUS 
SpyDetachDevice ( 
    IN PWSTR DeviceName 
    ) 
/*++ 
 
Routine Description: 
    This routine stop logging the specified device.  Since you can not 
    physically detatch 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 "detach" from. 
 
Return Value: 
    NT Status code 
 
--*/ 
{ 
    WCHAR             nameBuf[DEVICE_NAME_SZ]; 
    UNICODE_STRING    volumeNameUnicodeString; 
    PDEVICE_EXTENSION devext; 
 
 
    volumeNameUnicodeString.MaximumLength = sizeof(nameBuf); 
    volumeNameUnicodeString.Buffer = nameBuf; 
    volumeNameUnicodeString.Length= 0; 
 
 
    RtlAppendUnicodeToString(&volumeNameUnicodeString,DeviceName); 
    devext = SpyFindAttachedDevice(&volumeNameUnicodeString); 
    if (devext != NULL) { 
        devext->LogThisDevice = FALSE; 
        return STATUS_SUCCESS; 
    } 
    return STATUS_INVALID_PARAMETER; 
} 
 
 
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 physicall 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; 
    PDEVICE_EXTENSION pDevext; 
    PATTACHED_DEVICE pAttDev; 
    ULONG retlen = 0; 
 
    pAttDev = Buffer; 
 
    ExAcquireFastMutex( &gSpyDeviceExtensionListLock ); 
 
    for (link = gSpyDeviceExtensionList.Flink; 
         link != &gSpyDeviceExtensionList; 
         link = link->Flink) 
    { 
        pDevext = CONTAINING_RECORD(link, DEVICE_EXTENSION, NextDevice); 
 
        if (BufferSize < sizeof(ATTACHED_DEVICE)) 
            break; 
 
        pAttDev->LogState = pDevext->LogThisDevice; 
        wcscpy(pAttDev->DeviceName,pDevext->NameBuffer); 
        retlen += sizeof(ATTACHED_DEVICE); 
        BufferSize -= sizeof(ATTACHED_DEVICE); 
        pAttDev++; 
    } 
 
    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; 
    PRECORD_LIST pRecordList; 
    KIRQL        oldIrql; 
 
    IoStatus->Information = 0; 
 
    ExAcquireSpinLock(&gOutputBufferLock, &oldIrql); 
 
    while (!IsListEmpty( &gOutputBufferList ) && (length > 0)) { 
        pList = RemoveHeadList( &gOutputBufferList ); 
 
        pRecordList = CONTAINING_RECORD( pList, RECORD_LIST, List ); 
 
        pLogRecord = &pRecordList->LogRecord; 
 
        recordsAvailable++; 
 
        // make sure that the filename is always NULL terminated 
 
        if (REMAINING_NAME_SPACE(pRecordList) == MAX_NAME_SPACE) { 
            // We don't have a name, so return an empty string 
            pLogRecord->Length += sizeof(UNICODE_NULL); 
            pLogRecord->Name[0] = UNICODE_NULL; 
        } 
 
        pLogRecord->Name[(MAX_NAME_SPACE/sizeof(WCHAR)) - 1] = UNICODE_NULL; 
 
        // put it back if we've run out of room 
 
        if (length < pLogRecord->Length) { 
            InsertHeadList( &gOutputBufferList, pList ); 
            break; 
        } 
 
        ExReleaseSpinLock(&gOutputBufferLock, oldIrql); 
 
        RtlCopyMemory( pOutBuffer, pLogRecord, pLogRecord->Length ); 
 
        IoStatus->Information += pLogRecord->Length; 
        length -= pLogRecord->Length; 
        pOutBuffer += pLogRecord->Length; 
 
        SpyFreeRecord( pRecordList ); 
 
        ExAcquireSpinLock(&gOutputBufferLock, &oldIrql); 
    } 
 
    ExReleaseSpinLock(&gOutputBufferLock, oldIrql); 
 
    // no copies occured 
    if (length == OutputBufferLength && recordsAvailable > 0) { 
        IoStatus->Status = STATUS_BUFFER_TOO_SMALL; 
    } 
 
    return; 
} 
 
DBGSTATIC 
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. 
    // 
    ExAcquireSpinLock( &gControlDeviceStateLock, &oldIrql ); 
    gControlDeviceState = CLEANING_UP; 
    ExReleaseSpinLock( &gControlDeviceStateLock, oldIrql ); 
 
    ExAcquireSpinLock( &gOutputBufferLock, &oldIrql ); 
 
    while (!IsListEmpty( &gOutputBufferList )) { 
        pList = RemoveHeadList( &gOutputBufferList ); 
 
        ExReleaseSpinLock( &gOutputBufferLock, oldIrql ); 
 
        pRecordList = CONTAINING_RECORD( pList, RECORD_LIST, List ); 
 
        SpyFreeRecord( pRecordList ); 
 
        ExAcquireSpinLock( &gOutputBufferLock, &oldIrql ); 
    } 
 
    ExReleaseSpinLock( &gOutputBufferLock, oldIrql ); 
 
    SpyNameDeleteAllNames(); 
 
    // 
    // All the cleanup is done, so set the gControlDeviceState 
    // to CLOSED. 
    // 
    ExAcquireSpinLock( &gControlDeviceStateLock, &oldIrql ); 
    gControlDeviceState = CLOSED; 
    ExReleaseSpinLock( &gControlDeviceStateLock, oldIrql ); 
}