/*++ Copyright (c) 1989 Microsoft Corporation Module Name: DataSup.c Abstract: This module implements the mailslot data queue support functions. Author: Manny Weiser (mannyw) 9-Jan-1991 Revision History: --*/ #include "mailslot.h" // // The debug trace level // #define Dbg (DEBUG_TRACE_DATASUP) // // Local declarations // VOID MsCancelDataQueueIrp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); VOID MsResetCancelRoutine( IN PIRP Irp ); VOID MsSetCancelRoutine( IN PIRP Irp ); // // The following macro is used to dump a data queue // #define DumpDataQueue(S,P) { \ ULONG MsDumpDataQueue(IN ULONG Level, IN PDATA_QUEUE Ptr); \ DebugTrace(0,Dbg,S,0); \ DebugTrace(0,Dbg,"", MsDumpDataQueue(Dbg,P)); \ } #ifdef ALLOC_PRAGMA #pragma alloc_text( PAGE, MsAddDataQueueEntry ) #pragma alloc_text( PAGE, MsInitializeDataQueue ) #pragma alloc_text( PAGE, MsRemoveDataQueueEntry ) #pragma alloc_text( PAGE, MsUninitializeDataQueue ) #endif #if 0 NOT PAGEABLE -- MsCancelDataQueueIrp NOT PAGEABLE -- MsResetCancelRoutine NOT PAGEABLE -- MsSetCancelRoutine #endif VOID MsInitializeDataQueue ( IN PDATA_QUEUE DataQueue, IN PEPROCESS Process, IN ULONG Quota, IN ULONG MaximumMessageSize ) /*++ 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 to be initialized. Process - Supplies a pointer to the process creating the mailslot. Quota - Supplies the quota to assign to the data queue. MaximumMessageSize - The size of the largest message that can be written to the data queue. Return Value: None. --*/ { PAGED_CODE(); DebugTrace(+1, Dbg, "MsInitializeDataQueue, DataQueue = %08lx\n", (ULONG)DataQueue); // // Get the process's quota, if we can't get it then this call will // raise status. // ObReferenceObject( Process ); PsChargePoolQuota( Process, PagedPool, Quota ); // // Initialize the data queue structure. // DataQueue->BytesInQueue = 0; DataQueue->EntriesInQueue = 0; DataQueue->QueueState = Empty; DataQueue->MaximumMessageSize = MaximumMessageSize; DataQueue->Quota = Quota; DataQueue->QuotaUsed = 0; InitializeListHead( &DataQueue->DataEntryList ); // // Return to the caller. // DebugTrace(-1, Dbg, "MsInitializeDataQueue -> VOID\n", 0); return; } VOID MsUninitializeDataQueue ( 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 mailslot Return Value: None. --*/ { PAGED_CODE(); DebugTrace(+1, Dbg, "MsUninitializeDataQueue, DataQueue = %08lx\n", (ULONG)DataQueue); // // Assert that the queue is empty // ASSERT( IsListEmpty(&DataQueue->DataEntryList) ); // // Return all of our quota back to the process // PsReturnPoolQuota( Process, PagedPool, DataQueue->Quota ); ObDereferenceObject( Process ); // // For safety's sake, zero out the data queue structure. // RtlZeroMemory( DataQueue, sizeof(DATA_QUEUE ) ); // // Return to the caller. // DebugTrace(-1, Dbg, "MsUnininializeDataQueue -> VOID\n", 0); return; } PDATA_ENTRY MsAddDataQueueEntry ( IN PDATA_QUEUE DataQueue, IN QUEUE_STATE Who, IN ULONG DataSize, IN PIRP Irp ) /*++ Routine Description: This function adds a new data entry to the of the data queue. Entries are always appended to the queue. If necessary this function 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. *** This function must be called from within a try statement. 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 I/O completion +------+ +------>+------+ copy the buffer (Input operation) |User | |System| |Buffer| |Buffer| +------+ +------+ Who == WriteEntries && Quota Available +----------+ - Allocate Data Entry from Quota |Irp | +-----------+ | | |Buffered | - Allocate New System buffer | | |Quota | +----------+ +-----------+ - Copy data from User buffer to | | system buffer v v +------+ +------+ - Complete IRP |User |..copy..>|System| |Buffer| |Buffer| +------+ +------+ Who == WriteEntries && Quota 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 +------+ +------+ Arguments: DataQueue - Supplies the Data queue being modified. Who - Indicates if this is the reader or writer that is adding to the mailslot. DataSize - Indicates the size of the data buffer needed to represent this entry. Irp - Supplies a pointer to the IRP responsible for this entry. Return Value: PDATA_ENTRY - Returns a pointer to the newly added data entry. --*/ { PDATA_ENTRY dataEntry; PLIST_ENTRY previousEntry; PFCB fcb; PVOID rewind1 = NULL; PVOID rewind2 = NULL; BOOLEAN irpCompleted = FALSE; PAGED_CODE( ); DebugTrace(+1, Dbg, "MsAddDataQueueEntry, DataQueue = %08lx\n", (ULONG)DataQueue); ASSERT( DataQueue->QueueState != -1 ); try { if (Who == ReadEntries) { // // Allocate a data entry from the IRP, and allocate a new // system buffer. // dataEntry = (PDATA_ENTRY)IoGetNextIrpStackLocation( Irp ); dataEntry->DataPointer = NULL; dataEntry->Irp = Irp; dataEntry->DataSize = DataSize; dataEntry->TimeoutWorkContext = NULL; // // Check to see if the mailslot has enough quota left to // allocate the system buffer. // if ((DataQueue->Quota - DataQueue->QuotaUsed) >= DataSize) { // // Use the mailslot quota to allocate pool for the request. // rewind1 = dataEntry->DataPointer = FsRtlAllocatePool( PagedPool, DataSize == 0 ? 1 : DataSize ); DataQueue->QuotaUsed += DataSize; dataEntry->From = MailslotQuota; } else { // // Use the caller's quota to allocate pool for the request. // rewind1 = dataEntry->DataPointer = FsRtlAllocatePoolWithQuota( PagedPool, DataSize == 0 ? 1 : DataSize ); dataEntry->From = UserQuota; } // // Modify the IRP to be buffered I/O, deallocate the buffer, copy // the buffer on completion, and to reference the new system // buffer. // Irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION; Irp->AssociatedIrp.SystemBuffer = dataEntry->DataPointer; // // Setup to add this entry to the end of the data queue. // previousEntry = &DataQueue->DataEntryList; Irp->IoStatus.Status = (ULONG)DataQueue; } else { // // This is a writer entry. // // // If there is enough quota left in the mailslot then we will // allocate the data entry and data buffer from the mailslot // quota. // if ((DataQueue->Quota - DataQueue->QuotaUsed) >= sizeof(DATA_ENTRY) + DataSize) { // // Allocate the data buffer using the mailslot quota. // rewind2 = dataEntry = FsRtlAllocatePool( PagedPool, sizeof(DATA_ENTRY) ); rewind1 = dataEntry->DataPointer = FsRtlAllocatePool( PagedPool, DataSize == 0 ? 1 : DataSize ); DataQueue->QuotaUsed += sizeof(DATA_ENTRY) + DataSize; dataEntry->From = MailslotQuota; dataEntry->Irp = NULL; dataEntry->DataSize = DataSize; dataEntry->TimeoutWorkContext = NULL; } else { // // There isn't enough quota in the mailslot. Use the // caller's quota. // // Allocate a data entry from the Irp, and allocate a new // system buffer. // rewind2 = dataEntry = (PDATA_ENTRY)FsRtlAllocatePoolWithQuota( PagedPool, sizeof( DATA_ENTRY ) ); dataEntry->From = UserQuota; dataEntry->Irp = NULL; dataEntry->DataSize = DataSize; dataEntry->TimeoutWorkContext = NULL; rewind1 = dataEntry->DataPointer = FsRtlAllocatePoolWithQuota( PagedPool, DataSize == 0 ? 1 : DataSize ); } // // Copy the user buffer to the new system buffer, update the FCB // timestamps and complete the IRP. // try { RtlMoveMemory( dataEntry->DataPointer, Irp->UserBuffer, DataSize ); } except( EXCEPTION_EXECUTE_HANDLER ) { ExRaiseStatus( STATUS_INVALID_USER_BUFFER ); } fcb = CONTAINING_RECORD( DataQueue, FCB, DataQueue ); KeQuerySystemTime( &fcb->Specific.Fcb.LastModificationTime ); MsCompleteRequest( Irp, STATUS_SUCCESS ); irpCompleted = TRUE; // // Find the place in the queue to insert this data entry. // previousEntry = &DataQueue->DataEntryList; } // else (writer entry) // // 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. // if ( IsListEmpty( &DataQueue->DataEntryList ) ) { ASSERT( DataQueue->QueueState == Empty ); DataQueue->QueueState = Who; DataQueue->BytesInQueue = dataEntry->DataSize; DataQueue->EntriesInQueue = 1; } else { ASSERT( DataQueue->QueueState == Who ); DataQueue->BytesInQueue += dataEntry->DataSize; DataQueue->EntriesInQueue += 1; } // // Insert the new entry at the appropriate place in the data queue. // InsertTailList( previousEntry, &dataEntry->ListEntry ); } finally { if ( AbnormalTermination() ) { if ( rewind1 != NULL ) { ExFreePool( rewind1 ); } if ( rewind2 != NULL ) { ExFreePool( rewind2 ); } } // // If we have not already completed the IRP, see if the IRP has // been cancelled. // if ( !irpCompleted ) { MsSetCancelRoutine( Irp ); } } // // Return to the caller. // DumpDataQueue( "After AddDataQueueEntry\n", DataQueue ); DebugTrace(-1, Dbg, "MsAddDataQueueEntry -> %08lx\n", (ULONG)dataEntry); return dataEntry; } PIRP MsRemoveDataQueueEntry ( IN PDATA_QUEUE DataQueue, IN PDATA_ENTRY DataEntry ) /*++ Routine Description: This routine removes the specified data entry from the indicated data queue, and possibly returns the IRP associated with the entry if it wasn't already completed. 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: DataEntry - Supplies a pointer to the data entry to remove. Return Value: PIRP - Possibly returns a pointer to an IRP. --*/ { FROM from; PIRP irp; ULONG dataSize; PVOID dataPointer; PAGED_CODE( ); DebugTrace(+1, Dbg, "MsRemoveDataQueueEntry, DataEntry = %08lx\n", (ULONG)DataEntry); DebugTrace( 0, Dbg, "DataQueue = %08lx\n", (ULONG)DataQueue); // // Remove the data entry from the queue and update the count of // data entries in the queue. // --DataQueue->EntriesInQueue; RemoveEntryList( &DataEntry->ListEntry ); // // If the queue is now empty, update the state. // if (DataQueue->EntriesInQueue == 0) { DataQueue->QueueState = Empty; } // // Capture some of the fields from the data entry to make our // other references a little easier. // from = DataEntry->From; irp = DataEntry->Irp; dataSize = DataEntry->DataSize; dataPointer = DataEntry->DataPointer; // // Check if we need to deallocate the data buffer, we only need // to deallocate if the IRP is null. // if ( irp == NULL ) { ExFreePool( dataPointer ); } // // 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 == MailslotQuota) { DataQueue->QuotaUsed -= dataSize; } // // 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 mailslot or user quota, and we need to deallocate it ourselves. // if (irp != NULL) { DataEntry->From = 0; DataEntry->Irp = 0; DataEntry->DataSize = 0; DataEntry->DataPointer = 0; // // Null out the cancel routine // MsResetCancelRoutine( irp ); } else { ExFreePool( DataEntry ); // // 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 the mailslot quota. // if (from == MailslotQuota) { DataQueue->QuotaUsed -= sizeof(DATA_ENTRY); } } // // Return to the caller. // DumpDataQueue( "After RemoveDataQueueEntry\n", DataQueue ); DebugTrace(-1, Dbg, "MsRemoveDataQueueEntry -> %08lx\n", (ULONG)irp); return irp; } VOID MsCancelDataQueueIrp ( 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 - ignored 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. --*/ { PFCB fcb; PDATA_QUEUE dataQueue; PDATA_ENTRY dataEntry; PLIST_ENTRY listEntry, nextListEntry; PWORK_CONTEXT workContext; PKTIMER timer; UNREFERENCED_PARAMETER( DeviceObject ); // // 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 // spin-lock. // IoSetCancelRoutine( Irp, NULL ); IoReleaseCancelSpinLock( Irp->CancelIrql ); // // Get exclusive access to the mailslot FCB so we can now do our work. // fcb = CONTAINING_RECORD( dataQueue, FCB, DataQueue ); MsAcquireExclusiveFcb( fcb ); try { // // Scan the data queue looking for entries that have IRPs that // have been cancelled. // listEntry = dataQueue->DataEntryList.Flink; while ( listEntry != &dataQueue->DataEntryList ) { nextListEntry = listEntry->Flink; dataEntry = (PDATA_ENTRY)CONTAINING_RECORD( listEntry, DATA_ENTRY, ListEntry); // // If the data entry contains an Irp and the irp is cancelled then // we have some work to do. // if ( (dataEntry->Irp != NULL) && (dataEntry->Irp->Cancel)) { // // Cancel the timer, if there is one for this IRP. // workContext = dataEntry->TimeoutWorkContext; if (workContext != NULL) { DebugTrace( 0, Dbg, "Cancelling a timer\n", 0); // // There was a timer on this read operation. Attempt // to cancel the operation. If the cancel operation // is successful, then we must cleanup after the operation. // If it was unsuccessful the timer DPC will run, and // will eventually cleanup. // timer = &workContext->Timer; if (KeCancelTimer( timer ) ) { // // Release the reference to the FCB. // MsDereferenceFcb( workContext->Fcb ); // // Free the memory from the work context, the timer, // and the DPC. // ExFreePool( workContext ); } } // // Remove this data entry from the queue. // RemoveEntryList( listEntry ); --dataQueue->EntriesInQueue; // // If the queue is now empty then we need to fix the queue // state. // if (IsListEmpty( &dataQueue->DataEntryList ) ) { dataQueue->QueueState = Empty; } // // Check if we need to return mailslot quota. // if ( dataEntry->From == MailslotQuota ) { dataQueue->QuotaUsed -= dataEntry->DataSize; } // // Complete the request saying that it has been cancelled. // MsCompleteRequest( dataEntry->Irp, STATUS_CANCELLED ); } listEntry = nextListEntry; } } finally { MsReleaseFcb( fcb ); } // // And return to our caller // return; } // MsCancelDataQueueIrp VOID MsResetCancelRoutine( IN PIRP Irp ) /*++ Routine Description: Stub to null out the cancel routine. Arguments: Irp - Supplies the Irp whose cancel routine is to be nulled out. Return Value: None. --*/ { IoAcquireCancelSpinLock( &Irp->CancelIrql ); IoSetCancelRoutine( Irp, NULL ); IoReleaseCancelSpinLock( Irp->CancelIrql ); return; } // MsResetCancelRoutine VOID MsSetCancelRoutine( IN PIRP Irp ) /*++ Routine Description: Stub to set the cancel routine. If the irp has already been cancelled, the cancel routine is called. Arguments: Irp - Supplies the Irp whose cancel routine is to be set. Return Value: None. --*/ { IoAcquireCancelSpinLock( &Irp->CancelIrql ); if ( Irp->Cancel ) { // // The cancel spinlock will be released by this call. // MsCancelDataQueueIrp( NULL, Irp ); } else { IoSetCancelRoutine( Irp, MsCancelDataQueueIrp ); IoReleaseCancelSpinLock( Irp->CancelIrql ); } return; } // MsSetCancelRoutine