/*++ Copyright (c) 1989 Microsoft Corporation Module Name: dir.c Abstract: This module implements the file directory routines for the mailslot file system by the dispatch driver. Author: Manny Weiser (mannyw) 1-Feb-1991 Revision History: --*/ #include "mailslot.h" // // Local debug trace level // #define Dbg (DEBUG_TRACE_DIR) NTSTATUS MsCommonDirectoryControl ( IN PMSFS_DEVICE_OBJECT MsfsDeviceObject, IN PIRP Irp ); NTSTATUS MsQueryDirectory ( IN PROOT_DCB RootDcb, IN PROOT_DCB_CCB Ccb, IN PIRP Irp ); NTSTATUS MsNotifyChangeDirectory ( IN PROOT_DCB RootDcb, IN PROOT_DCB_CCB Ccb, IN PIRP Irp ); #ifdef ALLOC_PRAGMA #pragma alloc_text( PAGE, MsCommonDirectoryControl ) #pragma alloc_text( PAGE, MsFsdDirectoryControl ) #pragma alloc_text( PAGE, MsQueryDirectory ) #endif NTSTATUS MsFsdDirectoryControl ( IN PMSFS_DEVICE_OBJECT MsfsDeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine is the FSD routine that handles directory control functions (i.e., query and notify). Arguments: MsfsDeviceObject - Supplies the device object for the directory function. Irp - Supplies the IRP to process. Return Value: NTSTATUS - The result status. --*/ { NTSTATUS status; PAGED_CODE(); DebugTrace(+1, Dbg, "MsFsdDirectoryControl\n", 0); // // Call the common direcotry control routine. // FsRtlEnterFileSystem(); status = MsCommonDirectoryControl( MsfsDeviceObject, Irp ); FsRtlExitFileSystem(); // // Return to the caller. // DebugTrace(-1, Dbg, "MsFsdDirectoryControl -> %08lx\n", status ); return status; } VOID MsFlushNotifyForFile ( IN PDCB Dcb, IN PFILE_OBJECT FileObject ) /*++ Routine Description: This routine checks the notify queues of a DCB and completes any outstanding IRPS that match the given file object. This is used at cleanup time. Arguments: Dcb - Supplies the DCB to check for outstanding notify IRPs. FileObject - File object that IRP must be associated with. Return Value: None. --*/ { PLIST_ENTRY Links; PIRP Irp; KIRQL OldIrql; PLIST_ENTRY Head; PIO_STACK_LOCATION IrpSp; LIST_ENTRY CompletionList; Head = &Dcb->Specific.Dcb.NotifyFullQueue; InitializeListHead (&CompletionList); KeAcquireSpinLock (&Dcb->Specific.Dcb.SpinLock, &OldIrql); Links = Head->Flink; while (1) { if (Links == Head) { // // We are at the end of this queue. // if (Head == &Dcb->Specific.Dcb.NotifyFullQueue) { Head = &Dcb->Specific.Dcb.NotifyPartialQueue; Links = Head->Flink; if (Links == Head) { break; } } else { break; } } Irp = CONTAINING_RECORD( Links, IRP, Tail.Overlay.ListEntry ); IrpSp = IoGetCurrentIrpStackLocation( Irp ); // // If this IRP is for the matching file object then remove and save for completion // if (IrpSp->FileObject == FileObject) { Links = Links->Flink; RemoveEntryList (&Irp->Tail.Overlay.ListEntry); // // Remove cancel routine and detect if its already started running // if (IoSetCancelRoutine (Irp, NULL)) { // // Cancel isn't active and won't become active. // InsertTailList (&CompletionList, &Irp->Tail.Overlay.ListEntry); } else { // // Cancel is already active but is stalled before lock acquire. Initialize the // list head so the second remove is a noop. This is a rare case. // InitializeListHead (&Irp->Tail.Overlay.ListEntry); } } else { Links = Links->Flink; } } KeReleaseSpinLock (&Dcb->Specific.Dcb.SpinLock, OldIrql); while (!IsListEmpty (&CompletionList)) { Links = RemoveHeadList (&CompletionList); Irp = CONTAINING_RECORD( Links, IRP, Tail.Overlay.ListEntry ); MsCompleteRequest( Irp, STATUS_CANCELLED ); } return; } VOID MsCheckForNotify ( IN PDCB Dcb, IN BOOLEAN CheckAllOutstandingIrps, IN NTSTATUS FinalStatus ) /*++ Routine Description: This routine checks the notify queues of a DCB and completes any outstanding IRPS. Note that the caller of this procedure must guarantee that the DCB is acquired for exclusive access. Arguments: Dcb - Supplies the DCB to check for outstanding notify IRPs. CheckAllOutstandingIrps - Indicates if only the NotifyFullQueue should be checked. If TRUE then all notify queues are checked, and if FALSE then only the NotifyFullQueue is checked. Return Value: None. --*/ { PLIST_ENTRY links; PIRP irp; KIRQL OldIrql; PLIST_ENTRY Head; // // We'll always signal the notify full queue entries. They want // to be notified if every any change is made to a directory. // Head = &Dcb->Specific.Dcb.NotifyFullQueue; KeAcquireSpinLock (&Dcb->Specific.Dcb.SpinLock, &OldIrql); while (1) { links = RemoveHeadList (Head); if (links == Head) { // // This queue is empty. See if we need to skip to another. // if (Head == &Dcb->Specific.Dcb.NotifyFullQueue && CheckAllOutstandingIrps) { Head = &Dcb->Specific.Dcb.NotifyPartialQueue; links = RemoveHeadList (Head); if (links == Head) { break; } } else { break; } } // // Remove the Irp from the head of the queue, and complete it // with a success status. // irp = CONTAINING_RECORD( links, IRP, Tail.Overlay.ListEntry ); // // Remove cancel routine and detect if its already started running // if (IoSetCancelRoutine (irp, NULL)) { // // Cancel isn't active and won't become active. Release the spinlock for the complete. // KeReleaseSpinLock (&Dcb->Specific.Dcb.SpinLock, OldIrql); MsCompleteRequest( irp, FinalStatus ); KeAcquireSpinLock (&Dcb->Specific.Dcb.SpinLock, &OldIrql); } else { // // Cancel is already active but is stalled before lock acquire. Initialize the // list head so the second remove is a noop. This is a rare case. // InitializeListHead (&irp->Tail.Overlay.ListEntry); } } KeReleaseSpinLock (&Dcb->Specific.Dcb.SpinLock, OldIrql); return; } NTSTATUS MsCommonDirectoryControl ( IN PMSFS_DEVICE_OBJECT MsfsDeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine does the common code for directory control functions. Arguments: MsfsDeviceObject - Supplies the mailslot device object. Irp - Supplies the Irp being processed. Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS status; PIO_STACK_LOCATION irpSp; NODE_TYPE_CODE nodeTypeCode; PROOT_DCB_CCB ccb; PROOT_DCB rootDcb; PAGED_CODE(); // // Get the current stack location // irpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "CommonDirectoryControl...\n", 0); DebugTrace( 0, Dbg, "Irp = %08lx\n", (ULONG)Irp); // // Decode the file object to figure out who we are. If the result // is not the root DCB then its an illegal parameter. // if ((nodeTypeCode = MsDecodeFileObject( irpSp->FileObject, (PVOID *)&rootDcb, (PVOID *)&ccb )) == NTC_UNDEFINED) { DebugTrace(0, Dbg, "Not a directory\n", 0); MsCompleteRequest( Irp, STATUS_INVALID_PARAMETER ); status = STATUS_INVALID_PARAMETER; DebugTrace(-1, Dbg, "CommonDirectoryControl -> %08lx\n", status ); return status; } if (nodeTypeCode != MSFS_NTC_ROOT_DCB) { DebugTrace(0, Dbg, "Not a directory\n", 0); MsDereferenceNode( &rootDcb->Header ); MsCompleteRequest( Irp, STATUS_INVALID_PARAMETER ); status = STATUS_INVALID_PARAMETER; DebugTrace(-1, Dbg, "CommonDirectoryControl -> %08lx\n", status ); return status; } // // Acquire exclusive access to the root DCB. // MsAcquireExclusiveFcb( (PFCB)rootDcb ); // // Check if its been cleaned up yet. // status = MsVerifyDcbCcb (ccb); if (NT_SUCCESS (status)) { // // We know this is a directory control so we'll case on the // minor function, and call the appropriate work routines. // switch (irpSp->MinorFunction) { case IRP_MN_QUERY_DIRECTORY: status = MsQueryDirectory( rootDcb, ccb, Irp ); break; case IRP_MN_NOTIFY_CHANGE_DIRECTORY: status = MsNotifyChangeDirectory( rootDcb, ccb, Irp ); break; default: // // For all other minor function codes we say they're invalid // and complete the request. // DebugTrace(0, DEBUG_TRACE_ERROR, "Invalid FS Control Minor Function Code %08lx\n", irpSp->MinorFunction); status = STATUS_INVALID_DEVICE_REQUEST; break; } } MsReleaseFcb( (PFCB)rootDcb ); MsDereferenceRootDcb( rootDcb ); if (status != STATUS_PENDING) { MsCompleteRequest( Irp, status ); } return status; } NTSTATUS MsQueryDirectory ( IN PROOT_DCB RootDcb, IN PROOT_DCB_CCB Ccb, IN PIRP Irp ) /*++ Routine Description: This is the work routine for querying a directory. Arugments: RootDcb - Supplies the dcb being queried Ccb - Supplies the context of the caller Irp - Supplies the Irp being processed Return Value: NTSTATUS - The return status for the operation. --*/ { NTSTATUS status; PIO_STACK_LOCATION irpSp; PUCHAR buffer; CLONG systemBufferLength; UNICODE_STRING fileName; ULONG fileIndex; FILE_INFORMATION_CLASS fileInformationClass; BOOLEAN restartScan; BOOLEAN returnSingleEntry; BOOLEAN indexSpecified; #if 0 UNICODE_STRING unicodeString; ULONG unicodeStringLength; #endif BOOLEAN ansiStringAllocated = FALSE; static WCHAR star = L'*'; BOOLEAN caseInsensitive = TRUE; //*** Make searches case insensitive ULONG currentIndex; ULONG lastEntry; ULONG nextEntry; PLIST_ENTRY links; PFCB fcb; PFILE_DIRECTORY_INFORMATION dirInfo; PFILE_NAMES_INFORMATION namesInfo; PAGED_CODE(); // // Get the current stack location. // irpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "MsQueryDirectory\n", 0 ); DebugTrace( 0, Dbg, "RootDcb = %08lx\n", (ULONG)RootDcb); DebugTrace( 0, Dbg, "Ccb = %08lx\n", (ULONG)Ccb); DebugTrace( 0, Dbg, "SystemBuffer = %08lx\n", (ULONG)Irp->AssociatedIrp.SystemBuffer); DebugTrace( 0, Dbg, "Length = %08lx\n", irpSp->Parameters.QueryDirectory.Length); DebugTrace( 0, Dbg, "FileName = %08lx\n", (ULONG)irpSp->Parameters.QueryDirectory.FileName); DebugTrace( 0, Dbg, "FileIndex = %08lx\n", irpSp->Parameters.QueryDirectory.FileIndex); DebugTrace( 0, Dbg, "FileInformationClass = %08lx\n", irpSp->Parameters.QueryDirectory.FileInformationClass); DebugTrace( 0, Dbg, "RestartScan = %08lx\n", FlagOn(irpSp->Flags, SL_RESTART_SCAN)); DebugTrace( 0, Dbg, "ReturnSingleEntry = %08lx\n", FlagOn(irpSp->Flags, SL_RETURN_SINGLE_ENTRY)); DebugTrace( 0, Dbg, "IndexSpecified = %08lx\n", FlagOn(irpSp->Flags, SL_INDEX_SPECIFIED)); // // Make local copies of the input parameters. // systemBufferLength = irpSp->Parameters.QueryDirectory.Length; fileIndex = irpSp->Parameters.QueryDirectory.FileIndex; fileInformationClass = irpSp->Parameters.QueryDirectory.FileInformationClass; restartScan = FlagOn(irpSp->Flags, SL_RESTART_SCAN); indexSpecified = FlagOn(irpSp->Flags, SL_INDEX_SPECIFIED); returnSingleEntry = FlagOn(irpSp->Flags, SL_RETURN_SINGLE_ENTRY); if (irpSp->Parameters.QueryDirectory.FileName != NULL) { fileName = *(PUNICODE_STRING)irpSp->Parameters.QueryDirectory.FileName; // // Ensure that the name is reasonable // if( (fileName.Buffer == NULL && fileName.Length) || FlagOn( fileName.Length, 1 ) ) { status = STATUS_OBJECT_NAME_INVALID; return status; } } else { fileName.Length = 0; fileName.Buffer = NULL; } // // Check if the CCB already has a query template attached. If it // does not already have one then we either use the string we are // given or we attach our own containing "*" // if (Ccb->QueryTemplate == NULL) { // // This is our first time calling query directory so we need // to either set the query template to the user specified string // or to "*". // if (fileName.Buffer == NULL) { DebugTrace(0, Dbg, "Set template to *\n", 0); fileName.Length = sizeof( WCHAR ); fileName.Buffer = ☆ } DebugTrace(0, Dbg, "Set query template -> %wZ\n", (ULONG)&fileName); // // Allocate space for the query template. // Ccb->QueryTemplate = MsAllocatePagedPoolWithQuota ( sizeof(UNICODE_STRING) + fileName.Length, 'tFsM' ); if (Ccb->QueryTemplate == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; return status; } // // Initialize the query template and copy over the string. // Ccb->QueryTemplate->Length = fileName.Length; Ccb->QueryTemplate->Buffer = (PWCH)((PSZ)Ccb->QueryTemplate + sizeof(UNICODE_STRING)); RtlCopyMemory (Ccb->QueryTemplate->Buffer, fileName.Buffer, fileName.Length); // // Set the search to start at the beginning of the directory. // fileIndex = 0; } else { // // Check if we were given an index to start with or if we need to // restart the scan or if we should use the index that was saved in // the CCB. // if (restartScan) { fileIndex = 0; } else if (!indexSpecified) { fileIndex = Ccb->IndexOfLastCcbReturned + 1; } } // // Now we are committed to completing the Irp, we do that in // the finally clause of the following try. // try { ULONG baseLength; ULONG lengthAdded; BOOLEAN Match; // // Map the user buffer. // MsMapUserBuffer( Irp, KernelMode, (PVOID *)&buffer ); // // At this point we are about to enter our query loop. We have // already decided which Fcb index we need to return. The variables // LastEntry and NextEntry are used to index into the user buffer. // LastEntry is the last entry we added to the user buffer, and // NextEntry is the current one we're working on. CurrentIndex // is the Fcb index that we are looking at next. Logically the // way the loop works is as follows. // // Scan all of the Fcb in the directory // // if the Fcb matches the query template then // // if the CurrentIndex is >= the FileIndex then // // process this fcb, and decide if we should // continue the main loop // // end if // // Increment the current index // // end if // // end scan // currentIndex = 0; lastEntry = 0; nextEntry =0; switch (fileInformationClass) { case FileDirectoryInformation: baseLength = FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, FileName[0] ); break; case FileFullDirectoryInformation: baseLength = FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, FileName[0] ); break; case FileNamesInformation: baseLength = FIELD_OFFSET( FILE_NAMES_INFORMATION, FileName[0] ); break; case FileBothDirectoryInformation: baseLength = FIELD_OFFSET( FILE_BOTH_DIR_INFORMATION, FileName[0] ); break; default: try_return( status = STATUS_INVALID_INFO_CLASS ); } for (links = RootDcb->Specific.Dcb.ParentDcbQueue.Flink; links != &RootDcb->Specific.Dcb.ParentDcbQueue; links = links->Flink) { fcb = CONTAINING_RECORD(links, FCB, ParentDcbLinks); ASSERT(fcb->Header.NodeTypeCode == MSFS_NTC_FCB); DebugTrace(0, Dbg, "Top of Loop\n", 0); DebugTrace(0, Dbg, "Fcb = %08lx\n", (ULONG)fcb); DebugTrace(0, Dbg, "CurrentIndex = %08lx\n", currentIndex); DebugTrace(0, Dbg, "FileIndex = %08lx\n", fileIndex); DebugTrace(0, Dbg, "LastEntry = %08lx\n", lastEntry); DebugTrace(0, Dbg, "NextEntry = %08lx\n", nextEntry); // // Check if the Fcb represents a mailslot that is part of // our query template. // try { Match = FsRtlIsNameInExpression( Ccb->QueryTemplate, &fcb->LastFileName, caseInsensitive, NULL ); } except (EXCEPTION_EXECUTE_HANDLER) { try_return (status = GetExceptionCode ()); } if (Match) { // // The FCB is in the query template so now check if // this is the index we should start returning. // if (currentIndex >= fileIndex) { // // Yes it is one to return so case on the requested // information class. // ULONG bytesToCopy; ULONG bytesRemainingInBuffer; // // Here are the rules concerning filling up the buffer: // // 1. The Io system garentees that there will always be // enough room for at least one base record. // // 2. If the full first record (including file name) cannot // fit, as much of the name as possible is copied and // STATUS_BUFFER_OVERFLOW is returned. // // 3. If a subsequent record cannot completely fit into the // buffer, none of it (as in 0 bytes) is copied, and // STATUS_SUCCESS is returned. A subsequent query will // pick up with this record. // bytesRemainingInBuffer = systemBufferLength - nextEntry; if ( (nextEntry != 0) && ( (baseLength + fcb->LastFileName.Length > bytesRemainingInBuffer) || (systemBufferLength < nextEntry) ) ) { DebugTrace(0, Dbg, "Next entry won't fit\n", 0); try_return( status = STATUS_SUCCESS ); } ASSERT( bytesRemainingInBuffer >= baseLength ); // // See how much of the name we will be able to copy into // the system buffer. This also dictates out return // value. // if ( baseLength + fcb->LastFileName.Length <= bytesRemainingInBuffer ) { bytesToCopy = fcb->LastFileName.Length; status = STATUS_SUCCESS; } else { bytesToCopy = bytesRemainingInBuffer - baseLength; status = STATUS_BUFFER_OVERFLOW; } // // Note how much of buffer we are consuming and zero // the base part of the structure. // lengthAdded = baseLength + bytesToCopy; try { RtlZeroMemory( &buffer[nextEntry], baseLength ); switch (fileInformationClass) { case FileBothDirectoryInformation: // // We don't need short name // DebugTrace(0, Dbg, "Getting directory full information\n", 0); case FileFullDirectoryInformation: // // We don't use EaLength, so fill in nothing here. // DebugTrace(0, Dbg, "Getting directory full information\n", 0); case FileDirectoryInformation: DebugTrace(0, Dbg, "Getting directory information\n", 0); // // The eof indicates the number of instances and // allocation size is the maximum allowed // dirInfo = (PFILE_DIRECTORY_INFORMATION)&buffer[nextEntry]; dirInfo->FileAttributes = FILE_ATTRIBUTE_NORMAL; dirInfo->CreationTime = fcb->Specific.Fcb.CreationTime; dirInfo->LastAccessTime = fcb->Specific.Fcb.LastAccessTime; dirInfo->LastWriteTime = fcb->Specific.Fcb.LastModificationTime; dirInfo->ChangeTime = fcb->Specific.Fcb.LastChangeTime; dirInfo->FileNameLength = fcb->LastFileName.Length; break; case FileNamesInformation: DebugTrace(0, Dbg, "Getting names information\n", 0); namesInfo = (PFILE_NAMES_INFORMATION)&buffer[nextEntry]; namesInfo->FileNameLength = fcb->LastFileName.Length; break; default: KeBugCheck( MAILSLOT_FILE_SYSTEM ); } RtlCopyMemory (&buffer[nextEntry + baseLength], fcb->LastFileName.Buffer, bytesToCopy); // // Update the CCB to the index we've just used. // Ccb->IndexOfLastCcbReturned = currentIndex; // // And indicate how much of the system buffer we have // currently used up. We must compute this value before // we long align outselves for the next entry. // Irp->IoStatus.Information = nextEntry + lengthAdded; // // Setup the previous next entry offset. // *((PULONG)(&buffer[lastEntry])) = nextEntry - lastEntry; } except( EXCEPTION_EXECUTE_HANDLER ) { status = GetExceptionCode(); try_return( status ); } // // Check if the last entry didn't completely fit // if ( status == STATUS_BUFFER_OVERFLOW ) { try_return( NOTHING ); } // // Check if we are only to return a single entry // if (returnSingleEntry) { try_return( status = STATUS_SUCCESS ); } // // Set ourselves up for the next iteration // lastEntry = nextEntry; nextEntry += (ULONG)QuadAlign( lengthAdded ); } // // Increment the current index by one // currentIndex += 1; } } // // At this point we've scanned the entire list of FCBs so if // the NextEntry is zero then we haven't found anything so we // will return no more files, otherwise we return success. // if (nextEntry == 0) { status = STATUS_NO_MORE_FILES; } else { status = STATUS_SUCCESS; } try_exit: NOTHING; } finally { DebugTrace(-1, Dbg, "MsQueryDirectory -> %08lx\n", status); } return status; } VOID MsNotifyChangeDirectoryCancel ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This is the cancel routine for the directory notify request. Arugments: DeviceObject - Supplies the device object for the request being canceled. Irp - Supplies the Irp being canceled. Return Value: None --*/ { KIRQL OldIrql; PKSPIN_LOCK pSpinLock; // // First drop the cancel spinlock. We don't use this for this path // IoReleaseCancelSpinLock (Irp->CancelIrql); // // Grab the spinlock address. Easier that tracing the pointers or assuming that there is // only one DCB // pSpinLock = Irp->Tail.Overlay.DriverContext[0]; // // Acquire the spinlock protecting these queues. // KeAcquireSpinLock (pSpinLock, &OldIrql); // // Remove the entry from the list. We will always be in one of the lists or this entry has // been initializes as an empty list by one of the completion routines when it detected // this routine was active. // RemoveEntryList (&Irp->Tail.Overlay.ListEntry); KeReleaseSpinLock (pSpinLock, OldIrql); // // Complete the IRP // MsCompleteRequest( Irp, STATUS_CANCELLED ); return; } NTSTATUS MsNotifyChangeDirectory ( IN PROOT_DCB RootDcb, IN PROOT_DCB_CCB Ccb, IN PIRP Irp ) /*++ Routine Description: This is the common routine for doing the notify change directory. Arugments: RootDcb - Supplies the DCB being queried. Ccb - Supplies the context of the caller. Irp - Supplies the Irp being processed. Return Value: NTSTATUS - STATUS_PENDING or STATUS_CANCELLED --*/ { PIO_STACK_LOCATION irpSp; NTSTATUS Status; KIRQL OldIrql; PLIST_ENTRY Head; // // Get the current stack location. // irpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "MsNotifyChangeDirectory\n", 0 ); DebugTrace( 0, Dbg, "RootDcb = %p", RootDcb); DebugTrace( 0, Dbg, "Ccb = %p", Ccb); // // Mark the Irp pending. // if (irpSp->Parameters.NotifyDirectory.CompletionFilter & (~FILE_NOTIFY_CHANGE_NAME)) { Head = &RootDcb->Specific.Dcb.NotifyFullQueue; } else { Head = &RootDcb->Specific.Dcb.NotifyPartialQueue; } // // Make it easy for the cancel routine to find this spinlock // Irp->Tail.Overlay.DriverContext[0] = &RootDcb->Specific.Dcb.SpinLock; // // Acquire the spinlock protecting these queues. // KeAcquireSpinLock (&RootDcb->Specific.Dcb.SpinLock, &OldIrql); IoSetCancelRoutine (Irp, MsNotifyChangeDirectoryCancel); // // See if the IRP was already canceled before we enabled cancelation // if (Irp->Cancel && IoSetCancelRoutine (Irp, NULL) != NULL) { KeReleaseSpinLock (&RootDcb->Specific.Dcb.SpinLock, OldIrql); Status = STATUS_CANCELLED; } else { IoMarkIrpPending( Irp ); InsertTailList( Head, &Irp->Tail.Overlay.ListEntry ); KeReleaseSpinLock (&RootDcb->Specific.Dcb.SpinLock, OldIrql); Status = STATUS_PENDING; } // // Return to our caller. // DebugTrace(-1, Dbg, "NotifyChangeDirectory status %X\n", Status); return Status; }