/*++ Copyright (c) 1989 Microsoft Corporation Module Name: DataSup.c Abstract: This module implements the Named Pipe data queue support routines. Author: Gary Kimura [GaryKi] 30-Aug-1990 Revision History: --*/ #include "NpProcs.h" // // The debug trace level // #define Dbg (DEBUG_TRACE_DATASUP) #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, NpGetNextRealDataQueueEntry) #pragma alloc_text(PAGE, NpInitializeDataQueue) #pragma alloc_text(PAGE, NpUninitializeDataQueue) #endif // // The following macro is used to dump a data queue // #define DumpDataQueue(S,P) { \ ULONG NpDumpDataQueue(IN PDATA_QUEUE Ptr); \ DebugTrace(0,Dbg,S,0); \ DebugTrace(0,Dbg,"", NpDumpDataQueue(P)); \ } // // This is a debugging aid // _inline BOOLEAN NpfsVerifyDataQueue( IN ULONG Line, IN PDATA_QUEUE DataQueue ) { PDATA_ENTRY Entry; ULONG BytesInQueue = 0; ULONG EntriesInQueue = 0; for (Entry = DataQueue->FrontOfQueue; Entry != NULL; Entry = Entry->Next) { BytesInQueue += Entry->DataSize; EntriesInQueue += 1; if (Entry->Next == NULL) { if (Entry != DataQueue->EndOfQueue) { DbgPrint("%d DataQueue does not end corretly %08lx\n", Line, DataQueue ); DbgBreakPoint(); } } } if ((DataQueue->EntriesInQueue != EntriesInQueue) || (DataQueue->BytesInQueue != BytesInQueue)) { DbgPrint("%d DataQueue is illformed %08lx %x %x\n", Line, DataQueue, BytesInQueue, EntriesInQueue); DbgBreakPoint(); return FALSE; } return TRUE; } VOID NpCancelDataQueueIrp ( IN PDEVICE_OBJECT DevictObject, IN PIRP Irp ); VOID NpInitializeDataQueue ( IN PDATA_QUEUE DataQueue, IN PEPROCESS Process, IN ULONG Quota ) /*++ Routine Description: This routine initializes a new data queue. The indicated quota is taken from the process and not returned until the data queue is uninitialized. Arguments: DataQueue - Supplies the data queue being initialized Process - Supplies a pointer to the process creating the named pipe Quota - Supplies the quota to assign to the data queue Return Value: None. --*/ { PAGED_CODE(); DebugTrace(+1, Dbg, "NpInitializeDataQueue, DataQueue = %08lx\n", DataQueue); // // First thing we do is get the process's quota, if we can't get it // then this call will raise status // ObReferenceObject( Process ); PsChargePoolQuota( Process, NonPagedPool, Quota ); // // Now we can initialize the data queue structure // DataQueue->QueueState = Empty; DataQueue->BytesInQueue = 0; DataQueue->EntriesInQueue = 0; DataQueue->Quota = Quota; DataQueue->QuotaUsed = 0; DataQueue->FrontOfQueue = NULL; DataQueue->EndOfQueue = NULL; DataQueue->NextByteOffset = 0; // // And return to our caller // DebugTrace(-1, Dbg, "NpInitializeDataQueue -> VOID\n", 0); return; } VOID NpUninitializeDataQueue ( IN PDATA_QUEUE DataQueue, IN PEPROCESS Process ) /*++ Routine Description: This routine uninitializes a data queue. The previously debited quota is returned to the process. Arguments: DataQueue - Supplies the data queue being uninitialized Process - Supplies a pointer to the process who created the named pipe Return Value: None. --*/ { PAGED_CODE(); DebugTrace(+1, Dbg, "NpUninitializeDataQueue, DataQueue = %08lx\n", DataQueue); // // Assert that the queue is empty // ASSERT( DataQueue->QueueState == Empty ); // // Return all of our quota back to the process // PsReturnPoolQuota( Process, NonPagedPool, DataQueue->Quota ); ObDereferenceObject( Process ); // // Then for safety sake we'll zero out the data queue structure // RtlZeroMemory( DataQueue, sizeof(DATA_QUEUE ) ); // // And return to our caller // DebugTrace(-1, Dbg, "NpUnininializeDataQueue -> VOID\n", 0); return; } PDATA_ENTRY NpAddDataQueueEntry ( IN PDATA_QUEUE DataQueue, IN QUEUE_STATE Who, IN DATA_ENTRY_TYPE Type, IN ULONG DataSize, IN PIRP Irp OPTIONAL, IN PVOID DataPointer OPTIONAL ) /*++ Routine Description: This routine adds a new data entry to the end of the data queue. If necessary it will allocate a data entry buffer, or use space in the IRP, and possibly complete the indicated IRP. The different actions we are perform are based on the type and who parameters and quota requirements. Type == Internal (i.e, Unbuffered) +------+ - Allocate Data Entry from Irp |Irp | +----------+ | |<---|Unbuffered| - Reference Irp +------+ |InIrp | | +----------+ - Use system buffer from Irp v | +------+ | |System|<-----+ |Buffer| +------+ Type == Buffered && Who == ReadEntries +----------+ - Allocate Data Entry from Irp |Irp | +-----------+ |BufferedIo|<----|Buffered | - Allocate New System buffer |DeallBu...| |EitherQuota| +----------+ +-----------+ - Reference and modify Irp to | | | do Buffered I/O, Deallocate v | v buffer, and have io completion +------+ +------>+------+ copy the buffer (Input operation) |User | |System| |Buffer| |Buffer| +------+ +------+ Type == Buffered && Who == WriteEntries && PipeQuota Available +----------+ - Allocate Data Entry from Quota |Irp | +-----------+ | | |Buffered | - Allocate New System buffer | | |PipeQuota | +----------+ +-----------+ - Copy data from User buffer to | | system buffer v v +------+ +------+ - Complete Irp |User |..copy..>|System| |Buffer| |Buffer| +------+ +------+ Type == Buffered && Who == WriteEntries && PipeQuota Not Available +----------+ - Allocate Data Entry from Irp |Irp | +-----------+ |BufferedIo|<----|Buffered | - Allocate New System buffer |DeallBuff | |UserQuota | +----------+ +-----------+ - Reference and modify Irp to use | | | the new system buffer, do Buffered v | v I/O, and Deallocate buffer +------+ +------>+------+ |User | |System| - Copy data from User buffer to |Buffer|..copy..>|Buffer| system buffer +------+ +------+ Type == Flush or Close +----------+ - Allocate Data Entry from Irp |Irp | +-----------+ | |<----|Buffered | - Reference the Irp | | |UserQuota | +----------+ +-----------+ Arguments: DataQueue - Supplies the Data queue being modified Who - Indicates if this is the reader or writer that is adding to the pipe Type - Indicates the type of entry to add to the data queue DataSize - Indicates the size of the data buffer needed to represent this entry Irp - Supplies a pointer to the Irp responsible for this entry The irp is only optional for buffered write with available pipe quota DataPointer - If the Irp is not supplied then this field points to the user's write buffer. Return Value: PDATA_ENTRY - Returns a pointer to the newly added data entry --*/ { PDATA_ENTRY DataEntry; PIRP IrpToComplete; // // The following array indicates storage that needs to be deallocated // on an abnormal unwind. // PVOID Unwind[2] = { NULL, NULL }; ASSERT((DataQueue->QueueState == Empty) || (DataQueue->QueueState == Who)); DebugTrace(+1, Dbg, "NpAddDataQueueEntry, DataQueue = %08lx\n", DataQueue); IrpToComplete = NULL; try { // // Case on the type of operation we are doing // switch (Type) { case Unbuffered: ASSERT(ARGUMENT_PRESENT(Irp)); // // Allocate a data entry from the Irp // DataEntry = (PDATA_ENTRY)IoGetNextIrpStackLocation( Irp ); DataEntry->DataEntryType = Unbuffered; DataEntry->From = InIrp; DataEntry->Irp = Irp; DataEntry->DataSize = DataSize; DataEntry->DataPointer = Irp->AssociatedIrp.SystemBuffer; DataEntry->SecurityClientContext = NULL; ASSERT((DataQueue->QueueState == Empty) || (DataQueue->QueueState == Who)); break; case Buffered: // // Check if this is the reader or writer // if (Who == ReadEntries) { ASSERT(ARGUMENT_PRESENT(Irp)); // // Allocate a data entry from the Irp, and allocate a new // system buffer // DataEntry = (PDATA_ENTRY)IoGetNextIrpStackLocation( Irp ); DataEntry->DataEntryType = Buffered; DataEntry->Irp = Irp; DataEntry->DataSize = DataSize; if ((DataQueue->Quota - DataQueue->QuotaUsed) >= DataSize) { DataEntry->DataPointer = Unwind[0] = (DataSize != 0 ? FsRtlAllocatePool( NonPagedPool, DataSize ) : NULL); DataQueue->QuotaUsed += DataSize; DataEntry->From = PipeQuota; } else { DataEntry->DataPointer = Unwind[1] = (DataSize != 0 ? FsRtlAllocatePoolWithQuota( NonPagedPool, DataSize ) : NULL); DataEntry->From = UserQuota; } DataEntry->SecurityClientContext = NULL; // // Modify the Irp to be buffered I/O, deallocate the buffer, copy // the buffer on completion, and to reference the new system // buffer // if (DataSize != 0) { Irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION; Irp->AssociatedIrp.SystemBuffer = DataEntry->DataPointer; } ASSERT((DataQueue->QueueState == Empty) || (DataQueue->QueueState == Who)); } else { // // This is a writer entry // // // If there is enough quota left in the pipe then we will // allocate the data entry and data buffer from the pipe quota // if ((DataQueue->Quota - DataQueue->QuotaUsed) >= sizeof(DATA_ENTRY) + DataSize) { DataEntry = Unwind[0] = FsRtlAllocatePool( NonPagedPool, sizeof(DATA_ENTRY) ); DataEntry->DataPointer = Unwind[1] = (DataSize != 0 ? FsRtlAllocatePool( NonPagedPool, DataSize ) : NULL); DataQueue->QuotaUsed += sizeof(DATA_ENTRY) + DataSize; DataEntry->DataEntryType = Buffered; DataEntry->From = PipeQuota; DataEntry->Irp = NULL; DataEntry->DataSize = DataSize; DataEntry->SecurityClientContext = NULL; // // Safely copy the user buffer to the new system buffer using either // the irp user buffer is supplied of the data pointer we were given // if (ARGUMENT_PRESENT(Irp)) { DataPointer = Irp->UserBuffer; } try { RtlCopyMemory( DataEntry->DataPointer, DataPointer, DataSize ); } except(EXCEPTION_EXECUTE_HANDLER) { ExRaiseStatus( STATUS_INVALID_USER_BUFFER ); } IrpToComplete = Irp; ASSERT((DataQueue->QueueState == Empty) || (DataQueue->QueueState == Who)); } else { ASSERT(ARGUMENT_PRESENT(Irp)); // // There isn't enough pipe quota to do this so we will // use the user quota // // Allocate a data entry from the Irp, and allocate a new // system buffer // DataEntry = (PDATA_ENTRY)IoGetNextIrpStackLocation( Irp ); DataEntry->DataEntryType = Buffered; DataEntry->From = UserQuota; DataEntry->Irp = Irp; DataEntry->DataSize = DataSize; DataEntry->SecurityClientContext = NULL; DataEntry->DataPointer = Unwind[0] = (DataSize != 0 ? FsRtlAllocatePoolWithQuota( NonPagedPool, DataSize ) : NULL); // // Safely copy the user buffer to the new system buffer // try { RtlCopyMemory( DataEntry->DataPointer, Irp->UserBuffer, DataSize ); } except(EXCEPTION_EXECUTE_HANDLER) { ExRaiseStatus( STATUS_INVALID_USER_BUFFER ); } // // Modify the Irp to be buffered I/O, deallocate the buffer // and to reference the new system buffer // if (DataSize != 0) { Irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER; Irp->AssociatedIrp.SystemBuffer = DataEntry->DataPointer; } ASSERT((DataQueue->QueueState == Empty) || (DataQueue->QueueState == Who)); } } break; case Flush: case Close: ASSERT(ARGUMENT_PRESENT(Irp)); // // Allocate a data entry from the Irp // DataEntry = (PDATA_ENTRY)IoGetNextIrpStackLocation( Irp ); DataEntry->DataEntryType = Type; DataEntry->From = InIrp; DataEntry->Irp = Irp; DataEntry->DataSize = 0; DataEntry->DataPointer = NULL; DataEntry->SecurityClientContext = NULL; ASSERT((DataQueue->QueueState == Empty) || (DataQueue->QueueState == Who)); break; } ASSERT((DataQueue->QueueState == Empty) || (DataQueue->QueueState == Who)); // // Now data entry points to a new data entry to add to the data queue // Check if the queue is empty otherwise we will add this entry to // the end of the queue // DataEntry->Next = NULL; if (DataQueue->QueueState == Empty) { DataQueue->QueueState = Who; DataQueue->BytesInQueue = DataEntry->DataSize; DataQueue->EntriesInQueue = 1; DataQueue->FrontOfQueue = DataEntry; DataQueue->EndOfQueue = DataEntry; } else { ASSERT( DataQueue->QueueState == Who ); DataQueue->BytesInQueue += DataEntry->DataSize; DataQueue->EntriesInQueue += 1; DataQueue->EndOfQueue->Next = DataEntry; DataQueue->EndOfQueue = DataEntry; } } finally { // // If this is an abnormal termination then deallocate any storage // that we may have allocated // if (AbnormalTermination()) { if (Unwind[0] != NULL) { ExFreePool( Unwind[0] ); } if (Unwind[1] != NULL) { ExFreePool( Unwind[1] ); } } else { if (IrpToComplete != NULL) { NpCompleteRequest( IrpToComplete, STATUS_SUCCESS ); } else if (ARGUMENT_PRESENT(Irp)) { IoAcquireCancelSpinLock( &Irp->CancelIrql ); Irp->IoStatus.Status = (ULONG)DataQueue; if (Irp->Cancel) { // // Indicate in the first parameter that we're calling the // cancel routine and not the I/O system. Therefore // the routine won't take out the VCB exclusive. // NpCancelDataQueueIrp( ((PVOID)0x1), Irp ); } else { IoSetCancelRoutine( Irp, NpCancelDataQueueIrp ); IoReleaseCancelSpinLock( Irp->CancelIrql ); } } } DumpDataQueue( "After AddDataQueueEntry\n", DataQueue ); DebugTrace(-1, Dbg, "NpAddDataQueueEntry -> %08lx\n", DataEntry); } // // And return to our caller // return DataEntry; } PIRP NpRemoveDataQueueEntry ( IN PDATA_QUEUE DataQueue ) /*++ Routine Description: This routines remove the first entry from the front of the indicated data queue, and possibly returns the Irp associated with the entry if it wasn't already completed when we did the insert. If the data entry we are removing indicates buffered I/O then we also need to deallocate the data buffer besides the data entry but only if the Irp is null. Note that the data entry might be stored in an IRP. If it is then we are going to return the IRP it is stored in. Arguments: DataQueue - Supplies a pointer to the data queue being modifed Return Value: PIRP - Possibly returns a pointer to an IRP. --*/ { PDATA_ENTRY DataEntry; DATA_ENTRY_TYPE DataEntryType; FROM From; PIRP Irp; ULONG DataSize; PVOID DataPointer; PSECURITY_CLIENT_CONTEXT ClientContext; DebugTrace(+1, Dbg, "NpRemoveDataQueueEntry, DataQueue = %08lx\n", DataQueue); // // Check if the queue is empty, and if so then we simply return null // if (DataQueue->QueueState == Empty) { Irp = NULL; } else { // // Reference the front of the data queue, and remove the entry // from the queue itself. // DataEntry = DataQueue->FrontOfQueue; DataQueue->FrontOfQueue = DataEntry->Next; DataQueue->BytesInQueue -= DataEntry->DataSize; DataQueue->EntriesInQueue -= 1; // // Now if the queue is empty we need to reset the end of queue and // queue state // if (DataQueue->FrontOfQueue == NULL) { DataQueue->EndOfQueue = NULL; DataQueue->QueueState = Empty; } // // Capture some of the fields from the data entry to make our // other references a little easier // DataEntryType = DataEntry->DataEntryType; From = DataEntry->From; Irp = DataEntry->Irp; DataSize = DataEntry->DataSize; DataPointer = DataEntry->DataPointer; ClientContext = DataEntry->SecurityClientContext; // // Check if we should delete the client context // if (ClientContext != NULL) { SeDeleteClientSecurity( ClientContext ); ExFreePool( ClientContext ); } // // Check if we need to deallocate the data buffer, we only need // to deallocate it if it is buffered and the Irp is null // if (DataEntryType == Buffered) { if ((Irp == NULL) && (DataPointer != NULL)) { ExFreePool( DataPointer ); } // // Now the preceding call returned the user's quota or it // simply deallocated the buffer. If it only deallocated // the buffer then we need to credit our quota // if (From == PipeQuota) { DataQueue->QuotaUsed -= DataSize; } } // // Now check if we still have an IRP to return. If we do then // we know that this data entry is located in the current IRP // stack location and we need to zero out the data entry (skipping // over the spare field), otherwise we got allocated from either // the pipe or user quota, and we need to deallocate it ourselves. // // Note that we'll keep the data entry type field intact so that // out caller will know if this is an internal read operation. // if (Irp != NULL) { DataEntry->From = 0; DataEntry->Next = 0; DataEntry->Irp = 0; DataEntry->DataSize = 0; DataEntry->DataPointer = 0; DataEntry->SecurityClientContext = NULL; IoAcquireCancelSpinLock( &Irp->CancelIrql ); IoSetCancelRoutine( Irp, NULL ); IoReleaseCancelSpinLock( Irp->CancelIrql ); } else { if (DataPointer != NULL) { ExFreePool( DataEntry ); } // // Now the preceding call returned the user's quota or it // simply deallocated the buffer. If it only deallocated // the buffer then we need to credit our quota // if (From == PipeQuota) { DataQueue->QuotaUsed -= sizeof(DATA_ENTRY); } } } // // In all cases we'll also zero out the next byte offset. // DataQueue->NextByteOffset = 0; // // And return to our caller // DumpDataQueue( "After RemoveDataQueueEntry\n", DataQueue ); DebugTrace(-1, Dbg, "NpRemoveDataQueueEntry -> %08lx\n", Irp); return Irp; } PDATA_ENTRY NpGetNextRealDataQueueEntry ( IN PDATA_QUEUE DataQueue ) /*++ Routine Description: This routine will returns a pointer to the next real data queue entry in the indicated data queue. A real entry is either a read or write entry (i.e., buffered or unbuffered). It will complete (as necessary) any flush and close Irps that are in the queue until either the queue is empty or a real data queue entry is at the front of the queue. Arguments: DataQueue - Supplies a pointer to the data queue being modified Return Value: PDATA_ENTRY - Returns a pointer to the next data queue entry or NULL if there isn't any. --*/ { PDATA_ENTRY DataEntry; PIRP Irp; PAGED_CODE(); DebugTrace(+1, Dbg, "NpGetNextRealDataQueueEntry, DataQueue = %08lx\n", DataQueue); // // While the next data queue entry at the head of the data queue is not // a real data queue entry we'll dequeue that entry and complete // its corresponding IRP. // for (DataEntry = NpGetNextDataQueueEntry( DataQueue, NULL); (DataEntry != NULL) && ((DataEntry->DataEntryType != Buffered) && (DataEntry->DataEntryType != Unbuffered)); DataEntry = NpGetNextDataQueueEntry( DataQueue, NULL)) { // // We have a non real data queue entry that needs to be removed // and completed. // Irp = NpRemoveDataQueueEntry( DataQueue ); if (Irp != NULL) { NpCompleteRequest( Irp, STATUS_SUCCESS ); } } // // At this point we either have an empty data queue and data entry is // null, or we have a real data queue entry. In either case it // is time to return to our caller // DebugTrace(-1, Dbg, "NpGetNextRealDataQueueEntry -> %08lx\n", DataEntry); return DataEntry; } // // Local support routine // VOID NpCancelDataQueueIrp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine implements the cancel function for an IRP saved in a data queue Arguments: DeviceObject - Generally ignored but the low order bit is a flag indicating if we are being called locally (i.e., not from the I/O system) and therefore don't need to take out the VCB. Irp - Supplies the Irp being cancelled. A pointer to the data queue structure is stored in the information field of the Irp Iosb field. Return Value: None. --*/ { PDATA_QUEUE DataQueue; PDATA_ENTRY DataEntry; PDATA_ENTRY *Previous; // // The status field is used to store a pointer to the data queue // containing this irp // DataQueue = (PDATA_QUEUE)Irp->IoStatus.Status; // // We now need to void the cancel routine and release the io cancel spinlock // IoSetCancelRoutine( Irp, NULL ); IoReleaseCancelSpinLock( Irp->CancelIrql ); // // Get exclusive access to the named pipe vcb so we can now do our work // but only if we need to // if (DeviceObject != (PVOID)0x1) { NpAcquireExclusiveVcb(); } try { // // Scan through the data queue looking for entries that have Irps // that have been cancelled. We use previous to point to the pointer to // the data entry we're examining. We cannot do this in a for loop // because we'll have a tough time setting up ourselves for another iteration // when we remove an entry // Previous = &DataQueue->FrontOfQueue; DataEntry = *Previous; while (DataEntry != NULL) { // // If the data entry contains an Irp and the irp is cancelled then // we have some work do to // if ((DataEntry->Irp != NULL) && (DataEntry->Irp->Cancel)) { DATA_ENTRY_TYPE DataEntryType; FROM From; PIRP Irp; ULONG DataSize; PVOID DataPointer; PSECURITY_CLIENT_CONTEXT ClientContext; // // First remove this data entry from the queue // Later we will fixup Data entry to the next entry // *Previous = DataEntry->Next; // // If the queue is now empty then we need to fix the queue // state and end of queue pointer // if (DataQueue->FrontOfQueue == NULL) { DataQueue->EndOfQueue = NULL; DataQueue->QueueState = Empty; // // If we removed the last entry in the list then we need to update // the end of the queue // } else if (DataEntry == DataQueue->EndOfQueue) { DataQueue->EndOfQueue = (PDATA_ENTRY)Previous; } // // Capture some of the fields from the data entry to make our // other references a little easier // DataEntryType = DataEntry->DataEntryType; From = DataEntry->From; Irp = DataEntry->Irp; DataSize = DataEntry->DataSize; DataPointer = DataEntry->DataPointer; ClientContext = DataEntry->SecurityClientContext; // // Check if we should delete the client context // if (ClientContext != NULL) { SeDeleteClientSecurity( ClientContext ); ExFreePool( ClientContext ); } // // Check if we need to return pipe quota for a buffered entry. // if ((DataEntryType == Buffered) && (From == PipeQuota)) { DataQueue->QuotaUsed -= DataSize; } // // Update the data queue header information // DataQueue->BytesInQueue -= DataSize; DataQueue->EntriesInQueue -= 1; // // Zero our the data entry // DataEntry->From = 0; DataEntry->Next = 0; DataEntry->Irp = 0; DataEntry->DataSize = 0; DataEntry->DataPointer = 0; DataEntry->SecurityClientContext = NULL; // // If what we removed is in the front of the queue then we must // reset the next byte offset // if (Previous == &DataQueue->FrontOfQueue) { DataQueue->NextByteOffset = 0; } // // Finally complete the request saying that it has been cancelled. // NpCompleteRequest( Irp, STATUS_CANCELLED ); // // And because the data entry is gone we now need to reset // it so we can continue our while loop // DataEntry = *Previous; } else { // // Skip over to the next data entry // Previous = &DataEntry->Next; DataEntry = DataEntry->Next; } } } finally { if (DeviceObject != (PVOID)0x1) { NpReleaseVcb(); } } // // And return to our caller // return; }