/*++ Copyright (c) 1989 Microsoft Corporation Module Name: WaitSup.c Abstract: This module implements the Wait for Named Pipe support routines. Author: Gary Kimura [GaryKi] 30-Aug-1990 Revision History: --*/ #include "NpProcs.h" // // The debug trace level // #define Dbg (DEBUG_TRACE_WAITSUP) #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, NpInitializeWaitQueue) #pragma alloc_text(PAGE, NpUninitializeWaitQueue) #endif // // Local procedures and structures // typedef struct _WAIT_CONTEXT { PIRP Irp; KDPC Dpc; KTIMER Timer; PWAIT_QUEUE WaitQueue; UNICODE_STRING TranslatedString; PFILE_OBJECT FileObject; } WAIT_CONTEXT; typedef WAIT_CONTEXT *PWAIT_CONTEXT; VOID NpInitializeWaitQueue ( IN PWAIT_QUEUE WaitQueue ) /*++ Routine Description: This routine initializes the wait for named pipe queue. Arguments: WaitQueue - Supplies a pointer to the list head being initialized Return Value: None. --*/ { PAGED_CODE(); DebugTrace(+1, Dbg, "NpInitializeWaitQueue, WaitQueue = %08lx\n", WaitQueue); // // Initialize the List head // InitializeListHead( &WaitQueue->Queue ); // // Initialize the Wait Queue's spinlock // KeInitializeSpinLock( &WaitQueue->SpinLock ); // // and return to our caller // DebugTrace(-1, Dbg, "NpInitializeWaitQueue -> VOID\n", 0); return; } VOID NpUninitializeWaitQueue ( IN PWAIT_QUEUE WaitQueue ) /*++ Routine Description: This routine uninitializes the wait for named pipe queue. Arguments: WaitQueue - Supplies a pointer to the list head being uninitialized Return Value: None. --*/ { PAGED_CODE(); DebugTrace(+1, Dbg, "NpInitializeWaitQueue, WaitQueue = %08lx\n", WaitQueue); // // And return to our caller // DebugTrace(-1, Dbg, "NpInitializeWaitQueue -> VOID\n", 0); return; } NTSTATUS NpAddWaiter ( IN PWAIT_QUEUE WaitQueue, IN LARGE_INTEGER DefaultTimeOut, IN PIRP Irp, IN PUNICODE_STRING TranslatedString ) /*++ Routine Description: This routine adds a new "wait for named pipe" IRP to the wait queue. After calling this function the caller nolonger can access the IRP Arguments: WaitQueue - Supplies the wait queue being used DefaultTimeOut - Supplies the default time out to use if one is not supplied in the Irp Irp - Supplies a pointer to the wait Irp TranslatedString - If not NULL points to the translated string Return Value: None. --*/ { KIRQL OldIrql; PWAIT_CONTEXT Context; PFILE_PIPE_WAIT_FOR_BUFFER WaitForBuffer; LARGE_INTEGER Timeout; ULONG i; NTSTATUS status; PIO_STACK_LOCATION IrpSp; DebugTrace(+1, Dbg, "NpAddWaiter, WaitQueue = %08lx\n", WaitQueue); IrpSp = IoGetCurrentIrpStackLocation (Irp); // // Allocate a dpc and timer structure and initialize them // Context = NpAllocateNonPagedPoolWithQuota( sizeof(WAIT_CONTEXT), 'wFpN' ); if (Context == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } KeInitializeDpc( &Context->Dpc, NpTimerDispatch, Context ); KeInitializeTimer( &Context->Timer ); if (TranslatedString) { Context->TranslatedString = (*TranslatedString); } else { Context->TranslatedString.Length = 0; Context->TranslatedString.Buffer = NULL; } Context->WaitQueue = WaitQueue; Context->Irp = Irp; // // Figure out our timeout value // WaitForBuffer = (PFILE_PIPE_WAIT_FOR_BUFFER)Irp->AssociatedIrp.SystemBuffer; if (WaitForBuffer->TimeoutSpecified) { Timeout = WaitForBuffer->Timeout; } else { Timeout = DefaultTimeOut; } // // Upcase the name of the pipe we are waiting for // for (i = 0; i < WaitForBuffer->NameLength/sizeof(WCHAR); i += 1) { WaitForBuffer->Name[i] = RtlUpcaseUnicodeChar(WaitForBuffer->Name[i]); } NpIrpWaitQueue(Irp) = WaitQueue; NpIrpWaitContext(Irp) = Context; // // Acquire the spinlock // KeAcquireSpinLock( &WaitQueue->SpinLock, &OldIrql ); // // Now set the cancel routine for the irp and check if it has been cancelled. // IoSetCancelRoutine( Irp, NpCancelWaitQueueIrp ); if (Irp->Cancel && IoSetCancelRoutine( Irp, NULL ) != NULL) { status = STATUS_CANCELLED; } else { // // Now insert this new entry into the Wait Queue // InsertTailList( &WaitQueue->Queue, &Irp->Tail.Overlay.ListEntry ); IoMarkIrpPending (Irp); // // The DPC routine may run without an IRP if it gets completed before it runs. To keep the WaitQueue // valid we need a file object reference. This is an unload issue becuase the wait queue is in the VCB. // Context->FileObject = IrpSp->FileObject; ObReferenceObject (IrpSp->FileObject); // // And set the timer to go off // (VOID)KeSetTimer( &Context->Timer, Timeout, &Context->Dpc ); Context = NULL; status = STATUS_PENDING; } // // Release the spinlock // KeReleaseSpinLock( &WaitQueue->SpinLock, OldIrql ); if (Context != NULL) { NpFreePool (Context); } // // And now return to our caller // DebugTrace(-1, Dbg, "NpAddWaiter -> VOID\n", 0); return status; } NTSTATUS NpCancelWaiter ( IN PWAIT_QUEUE WaitQueue, IN PUNICODE_STRING NameOfPipe, IN NTSTATUS Completionstatus, IN PLIST_ENTRY DeferredList ) /*++ Routine Description: This procedure cancels all waiters that are waiting for the named pipe to reach the listening state. The corresponding IRPs are completed with Completionstatus. Arguments: WaitQueue - Supplies the wait queue being modified NameOfPipe - Supplies the name of the named pipe (device relative) that has just reached the listening state. CompletionStatus - Status to complete IRPs with DeferredList - List or IRPs to complete once we drop locks Return Value: None. --*/ { KIRQL OldIrql; PLIST_ENTRY Links; PIRP Irp; PFILE_PIPE_WAIT_FOR_BUFFER WaitForBuffer; PWAIT_CONTEXT Context, ContextList= NULL; ULONG i; BOOLEAN SuccessfullMatch = FALSE; UNICODE_STRING NonPagedNameOfPipe; DebugTrace(+1, Dbg, "NpCancelWaiter, WaitQueue = %08lx\n", WaitQueue); // // Capture the name of pipe before we grab the spinlock, and upcase it // NonPagedNameOfPipe.Buffer = NpAllocateNonPagedPool( NameOfPipe->Length, 'tFpN' ); if (NonPagedNameOfPipe.Buffer == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } NonPagedNameOfPipe.Length = 0; NonPagedNameOfPipe.MaximumLength = NameOfPipe->Length; (VOID) RtlUpcaseUnicodeString( &NonPagedNameOfPipe, NameOfPipe, FALSE ); // // Acquire the spinlock // KeAcquireSpinLock( &WaitQueue->SpinLock, &OldIrql ); // // For each waiting irp check if the name matches // for (Links = WaitQueue->Queue.Flink; Links != &WaitQueue->Queue; Links = Links->Flink) { Irp = CONTAINING_RECORD( Links, IRP, Tail.Overlay.ListEntry ); WaitForBuffer = (PFILE_PIPE_WAIT_FOR_BUFFER)Irp->AssociatedIrp.SystemBuffer; Context = NpIrpWaitContext(Irp); // // Check if this Irp matches the one we've been waiting for // First check the lengths for equality, and then compare // the strings. They match if we exit the inner loop with // i >= name length. // SuccessfullMatch = FALSE; if (Context->TranslatedString.Length ) { if (NonPagedNameOfPipe.Length == Context->TranslatedString.Length) { if (RtlEqualMemory(Context->TranslatedString.Buffer, NonPagedNameOfPipe.Buffer, NonPagedNameOfPipe.Length)) { SuccessfullMatch = TRUE; } } } else { if (((USHORT)(WaitForBuffer->NameLength + sizeof(WCHAR))) == NonPagedNameOfPipe.Length) { for (i = 0; i < WaitForBuffer->NameLength/sizeof(WCHAR); i += 1) { if (WaitForBuffer->Name[i] != NonPagedNameOfPipe.Buffer[i+1]) { break; } } if (i >= WaitForBuffer->NameLength/sizeof(WCHAR)) { SuccessfullMatch = TRUE; } } } if (SuccessfullMatch) { Links = Links->Blink; RemoveEntryList( &Irp->Tail.Overlay.ListEntry ); // // Attempt to stop the timer. If its already running then it must be stalled before obtaining // this spinlock or it would have removed this item from the list. Break the link between the timer // context and the IRP in this case and let it run on. // if (KeCancelTimer( &Context->Timer )) { // // Time got stopped. The context gets freed below after we drop the lock. // Context->WaitQueue = (PWAIT_QUEUE) ContextList; ContextList = Context; } else { // // Break the link between the timer and the IRP // Context->Irp = NULL; NpIrpWaitContext(Irp) = NULL; } // // Remove cancelation. If its already running then let it complete the IRP. // if (IoSetCancelRoutine( Irp, NULL ) != NULL) { Irp->IoStatus.Information = 0; NpDeferredCompleteRequest (Irp, Completionstatus, DeferredList); } else { // // Cancel is already running. Let it complete this IRP but let it know its orphaned. // NpIrpWaitContext(Irp) = NULL; } } } // // Release the spinlock // KeReleaseSpinLock( &WaitQueue->SpinLock, OldIrql ); NpFreePool (NonPagedNameOfPipe.Buffer); while (ContextList != NULL) { Context = ContextList; ContextList = (PWAIT_CONTEXT) Context->WaitQueue; ObDereferenceObject (Context->FileObject); NpFreePool( Context ); } DebugTrace(-1, Dbg, "NpCancelWaiter -> VOID\n", 0); // // And now return to our caller // return STATUS_SUCCESS; } // // Local support routine // VOID NpTimerDispatch( IN PKDPC Dpc, IN PVOID Contxt, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) /*++ Routine Description: This routine is called whenever a timer on a wait queue Irp goes off Arguments: Dpc - Ignored Contxt - Supplies a pointer to the context whose timer went off SystemArgument1 - Ignored SystemArgument2 - Ignored Return Value: none. --*/ { PIRP Irp; KIRQL OldIrql; PLIST_ENTRY Links; PWAIT_CONTEXT Context; PWAIT_QUEUE WaitQueue; UNREFERENCED_PARAMETER( Dpc ); UNREFERENCED_PARAMETER( SystemArgument1 ); UNREFERENCED_PARAMETER( SystemArgument2 ); Context = (PWAIT_CONTEXT)Contxt; WaitQueue = Context->WaitQueue; KeAcquireSpinLock( &WaitQueue->SpinLock, &OldIrql ); Irp = Context->Irp; if (Irp != NULL) { RemoveEntryList( &Irp->Tail.Overlay.ListEntry ); if (IoSetCancelRoutine (Irp, NULL) == NULL) { // // Cancel started running. Let it complete the IRP but show its orphaned // NpIrpWaitContext(Irp) = NULL; Irp = NULL; } } KeReleaseSpinLock( &WaitQueue->SpinLock, OldIrql ); if (Irp != NULL) { NpCompleteRequest( Irp, STATUS_IO_TIMEOUT ); } // // Remove the file reference now we have finished with the wait queue. // ObDereferenceObject (Context->FileObject); // // Deallocate the context // NpFreePool (Context); // // And now return to our caller // return; } // // Local Support routine // VOID NpCancelWaitQueueIrp( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine is called to cancel a wait queue irp Arguments: DeviceObject - Ignored Irp - Supplies the Irp being cancelled. The Iosb.Status field in the irp points to the wait queue Return Value: none. --*/ { PWAIT_QUEUE WaitQueue; KIRQL OldIrql; PLIST_ENTRY Links; PWAIT_CONTEXT Context; UNREFERENCED_PARAMETER( DeviceObject ); IoReleaseCancelSpinLock( Irp->CancelIrql ); // // The status field is used to store a pointer to the wait queue // containing this irp // WaitQueue = NpIrpWaitQueue(Irp); // // Get the spinlock proctecting the wait queue // KeAcquireSpinLock( &WaitQueue->SpinLock, &OldIrql ); Context = NpIrpWaitContext(Irp); if (Context != NULL) { RemoveEntryList (&Irp->Tail.Overlay.ListEntry); if (!KeCancelTimer( &Context->Timer )) { // // Timer is already running. Break the link between the timer and the IRP as this thread is going to complete it. // Context->Irp = NULL; Context = NULL; } } KeReleaseSpinLock( &WaitQueue->SpinLock, OldIrql ); if (Context) { ObDereferenceObject (Context->FileObject); NpFreePool (Context); } Irp->IoStatus.Information = 0; NpCompleteRequest( Irp, STATUS_CANCELLED ); // // And return to our caller // return; }