Windows2000/private/ntos/lpc/lpcqueue.c
2020-09-30 17:12:32 +02:00

543 lines
20 KiB
C

/*++
Copyright (c) 1989 Microsoft Corporation
Module Name:
lpcqueue.c
Abstract:
Local Inter-Process Communication (LPC) queue support routines.
Author:
Steve Wood (stevewo) 15-May-1989
--*/
#include "lpcp.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT,LpcpInitializePortZone)
#pragma alloc_text(PAGE,LpcpInitializePortQueue)
#pragma alloc_text(PAGE,LpcpDestroyPortQueue)
#pragma alloc_text(PAGE,LpcpExtendPortZone)
#pragma alloc_text(PAGE,LpcpAllocateFromPortZone)
#pragma alloc_text(PAGE,LpcpFreeToPortZone)
#pragma alloc_text(PAGE,LpcpSaveDataInfoMessage)
#pragma alloc_text(PAGE,LpcpFreeDataInfoMessage)
#pragma alloc_text(PAGE,LpcpFindDataInfoMessage)
#endif
NTSTATUS LpcpInitializePortQueue (IN PLPCP_PORT_OBJECT Port)
/*++
Routine Description:
This routine is used to initialize the message queue for a port object.
Arguments:
Port - Supplies the port object being initialized
Return Value:
NTSTATUS - An appropriate status value
--*/
{
PLPCP_NONPAGED_PORT_QUEUE NonPagedPortQueue;
PAGED_CODE();
// Allocate space for the port queue
NonPagedPortQueue = ExAllocatePoolWithTag( NonPagedPool, sizeof(LPCP_NONPAGED_PORT_QUEUE), 'troP' );
if (NonPagedPortQueue == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
// Initialize the fields in the non paged port queue
KeInitializeSemaphore( &NonPagedPortQueue->Semaphore, 0, 0x7FFFFFFF );
NonPagedPortQueue->BackPointer = Port;
Port->MsgQueue.Semaphore = &NonPagedPortQueue->Semaphore;// Have the port msg queue point to the non nonpaged port queue
InitializeListHead( &Port->MsgQueue.ReceiveHead );// Initailize the port msg queue to be empty
return STATUS_SUCCESS;// And return to our caller
}
VOID LpcpDestroyPortQueue (IN PLPCP_PORT_OBJECT Port, IN BOOLEAN CleanupAndDestroy)
/*++
Routine Description:
This routine is used to teardown the message queue of a port object.
After running this message will either be empty (like it was just initialized) or completly gone (needs to be initialized)
Arguments:
Port - Supplies the port containing the message queue being modified
CleanupAndDestroy - Specifies if the message queue should be set back to the freshly initialized state (value of FALSE) or completely torn down (value of TRUE)
--*/
{
PLIST_ENTRY Next, Head;
PETHREAD ThreadWaitingForReply;
PLPCP_MESSAGE Msg;
PAGED_CODE();
// If this port is connected to another port, then disconnect it.
// Protect this with a lock in case the other side is going away at the same time.
LpcpAcquireLpcpLock();
if (Port->ConnectedPort != NULL) {
Port->ConnectedPort->ConnectedPort = NULL;
}
// If connection port, then mark name as deleted
if ((Port->Flags & PORT_TYPE) == SERVER_CONNECTION_PORT) {
Port->Flags |= PORT_NAME_DELETED;
}
// Walk list of threads waiting for a reply to a message sent to this port.
// Signal each thread's LpcReplySemaphore to wake them up.
// They will notice that there was no reply and return STATUS_PORT_DISCONNECTED
Head = &Port->LpcReplyChainHead;
Next = Head->Flink;
while ((Next != NULL) && (Next != Head)) {
ThreadWaitingForReply = CONTAINING_RECORD( Next, ETHREAD, LpcReplyChain );
// If the thread is exiting, in the location of LpcReplyChain is stored the ExitTime
// We'll stop to search throught the list.
if ( ThreadWaitingForReply->LpcExitThreadCalled ) {
break;
}
Next = Next->Flink;
RemoveEntryList( &ThreadWaitingForReply->LpcReplyChain );
InitializeListHead( &ThreadWaitingForReply->LpcReplyChain );
if (!KeReadStateSemaphore( &ThreadWaitingForReply->LpcReplySemaphore )) {
// Thread is waiting on a message. Signal the semaphore and free the message
Msg = ThreadWaitingForReply->LpcReplyMessage;
if ( Msg ) {
// If the message is a connection request and has a section object attached, then dereference that section object
if ((Msg->Request.u2.s2.Type & ~LPC_KERNELMODE_MESSAGE) == LPC_CONNECTION_REQUEST) {
PLPCP_CONNECTION_MESSAGE ConnectMsg;
ConnectMsg = (PLPCP_CONNECTION_MESSAGE)(Msg + 1);
if ( ConnectMsg->SectionToMap != NULL ) {
ObDereferenceObject( ConnectMsg->SectionToMap );
}
}
ThreadWaitingForReply->LpcReplyMessage = NULL;
LpcpFreeToPortZone( Msg, TRUE );
}
ThreadWaitingForReply->LpcReplyMessageId = 0;
KeReleaseSemaphore( &ThreadWaitingForReply->LpcReplySemaphore, 0, 1L, FALSE );
}
}
InitializeListHead( &Port->LpcReplyChainHead );
// Walk list of messages queued to this port. Remove each message from the list and free it.
Head = &Port->MsgQueue.ReceiveHead;
Next = Head->Flink;
while ((Next != NULL) && (Next != Head)) {
Msg = CONTAINING_RECORD( Next, LPCP_MESSAGE, Entry );
Next = Next->Flink;
InitializeListHead( &Msg->Entry );
LpcpFreeToPortZone( Msg, TRUE );
}
InitializeListHead( &Port->MsgQueue.ReceiveHead );// Reinitialize the message queue
LpcpReleaseLpcpLock();
if ( CleanupAndDestroy ) {// Check if the caller wants it all to go away
if (Port->MsgQueue.Semaphore != NULL) {// Free semaphore associated with the queue.
ExFreePool( CONTAINING_RECORD( Port->MsgQueue.Semaphore, LPCP_NONPAGED_PORT_QUEUE, Semaphore ));
}
}
return;// And return to our caller
}
NTSTATUS LpcpInitializePortZone (IN ULONG MaxEntrySize, IN ULONG SegmentSize, IN ULONG MaxPoolUsage)
/*++
Routine Description:
This routine only executes once. It is used to initialize the zone allocator for LPC messages
Arguments:
MaxEntrySize - Specifies the maximum size, in bytes, that we'll allocate from the zone.
SegmentSize - Specifies the number of total bytes to use in the initial zone allocation.
This is also the increment use to grow the zone.
MaxPoolUsage - Specifies the maximum number of bytes we every want to zone to grow to.
Return Value:
NTSTATUS - An appropriate status value
--*/
{
NTSTATUS Status;
PVOID Segment;
PLPCP_MESSAGE Msg;
LONG SegSize;
PAGED_CODE();
// Set the sizes in the global port zone variable
LpcpZone.MaxPoolUsage = MaxPoolUsage;
LpcpZone.GrowSize = SegmentSize;
// Allocate and initialize the initial zone
Segment = ExAllocatePoolWithTag( PagedPool, SegmentSize, 'ZcpL' );
if (Segment == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
KeInitializeEvent( &LpcpZone.FreeEvent, SynchronizationEvent, FALSE );
Status = ExInitializeZone( &LpcpZone.Zone, MaxEntrySize, Segment, SegmentSize );
if (!NT_SUCCESS( Status )) {
ExFreePool( Segment );
}
// The following code sets up each msg in the zone by filling in its index, and zeroing the rest.
// **** note, this is really a backdoor piece of code. First, it assumes
// segsize is pagesize, and second, it assume the zone doesn't touch those fields for its own bookkeeping.
SegSize = PAGE_SIZE;
LpcpTotalNumberOfMessages = 0;
// Skip over the segment header
Msg = (PLPCP_MESSAGE)((PZONE_SEGMENT_HEADER)Segment + 1);
// For each block in the zone pointed at by Msg, pre-initialize the Msg
while (SegSize >= (LONG)LpcpZone.Zone.BlockSize) {
Msg->ZoneIndex = (USHORT)++LpcpTotalNumberOfMessages;
Msg->Reserved0 = 0;
Msg->Request.MessageId = 0;
Msg = (PLPCP_MESSAGE)((PCHAR)Msg + LpcpZone.Zone.BlockSize);
SegSize -= LpcpZone.Zone.BlockSize;
}
return Status;// And return to our caller
}
NTSTATUS LpcpExtendPortZone (VOID)
/*++
Routine Description:
This routine is used to increase the size of the global port zone.
Note that our caller must have already acquired the LpcpLock mutex.
Return Value:
NTSTATUS - An appropriate status value
--*/
{
NTSTATUS Status;
PVOID Segment;
PLPCP_MESSAGE Msg;
LARGE_INTEGER WaitTimeout;
BOOLEAN AlreadyRetried;
LONG SegmentSize;
PAGED_CODE();
AlreadyRetried = FALSE;
retry:
// If we're going to grow across a threshold, we should wait a little in case an entry gets freed.
if ((LpcpZone.Zone.TotalSegmentSize + LpcpZone.GrowSize) > LpcpZone.MaxPoolUsage) {
LpcpPrint(( "Out of space in global LPC zone - current size is %08x\n", LpcpZone.Zone.TotalSegmentSize ));
// Wait time is 1 second.
// We'll actually only go through this path once because on retry we bump up max pool usage to avoid this wait
WaitTimeout.QuadPart = Int32x32To64( 1000, -10000 );
LpcpReleaseLpcpLock();
Status = KeWaitForSingleObject( &LpcpZone.FreeEvent, Executive, KernelMode, FALSE, &WaitTimeout );
LpcpAcquireLpcpLock();
if (Status != STATUS_SUCCESS) {
LpcpPrint(( "Error waiting for %lx->FreeEvent - Status == %X\n", &LpcpZone, Status ));
if ( !AlreadyRetried ) {
AlreadyRetried = TRUE;
LpcpZone.MaxPoolUsage += LpcpZone.GrowSize;
goto retry;
}
}
return Status;// return to our caller and let the caller retry the allocation again
}
// We need to extend the zone, so allocate another chunk and add it to the zone
Segment = ExAllocatePoolWithTag( PagedPool, LpcpZone.GrowSize, 'ZcpL' );
if (Segment == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = ExExtendZone( &LpcpZone.Zone, Segment, LpcpZone.GrowSize );
if (!NT_SUCCESS( Status )) {
ExFreePool( Segment );
}
#if DEVL
else {
LpcpTrace(( "Extended LPC zone by %x for a total of %x\n", LpcpZone.GrowSize, LpcpZone.Zone.TotalSegmentSize ));
// The following code sets up each msg in the zone by filling in its index, and zeroing the rest.
// **** note, this is really a backdoor piece of code.
// First, it assumes we grew by one page, and second, it assume the zone doesn't touch those fields for its own bookkeeping.
SegmentSize = PAGE_SIZE;
Msg = (PLPCP_MESSAGE)((PZONE_SEGMENT_HEADER)Segment + 1);
while (SegmentSize >= (LONG)LpcpZone.Zone.BlockSize) {
Msg->ZoneIndex = (USHORT)++LpcpTotalNumberOfMessages;
Msg = (PLPCP_MESSAGE)((PCHAR)Msg + LpcpZone.Zone.BlockSize);
SegmentSize -= LpcpZone.Zone.BlockSize;
}
// Now that we've completely initialized the new segment, go and wake up any waiters
LpcpReleaseLpcpLock();
KeSetEvent( &LpcpZone.FreeEvent, LPC_RELEASE_WAIT_INCREMENT, FALSE );
LpcpAcquireLpcpLock();
}
#endif
return Status;
}
PLPCP_MESSAGE FASTCALL LpcpAllocateFromPortZone (ULONG Size)
/*++
Routine Description:
This procedure is used to allocate a new msg from the global port zone.
Note that we assume our caller owns the LpcpLock mutex.
Arguments:
Size - Specifies the size, in bytes, needed for the allocation. Currently this variable is ignored
Return Value:
PLPCP_MESSAGE - returns a pointer to the newly allocate message
--*/
{
NTSTATUS Status;
PLPCP_MESSAGE Msg;
PAGED_CODE();
// Continue looping until we get a message to give back or until when we extend the zone we get back and error
do {
// Pick off the next message from the zone and if we actually did get one then initialize some of its fields and return it to our caller
Msg = (PLPCP_MESSAGE)ExAllocateFromZone( &LpcpZone.Zone );
if (Msg != NULL) {
LpcpTrace(( "Allocate Msg %lx\n", Msg ));
InitializeListHead( &Msg->Entry );
Msg->RepliedToThread = NULL;
#if DBG
Msg->ZoneIndex |= LPCP_ZONE_MESSAGE_ALLOCATED;// In the debug case mark the message as allocated
#endif
return Msg;
}
// The zone didn't give us an entry so extend the zone and try the allocation again
LpcpTrace(( "Extending Zone %lx\n", &LpcpZone.Zone ));
Status = LpcpExtendPortZone( );
} while (NT_SUCCESS(Status));
return NULL;// Return NULL (i.e., an error) to our caller
}
VOID FASTCALL LpcpFreeToPortZone (IN PLPCP_MESSAGE Msg, IN BOOLEAN MutexOwned)
/*++
Routine Description:
This routine is used to free an old msg back to the global message queue
Arguments:
Msg - Supplies the message being returned
MutexOwned - Specifies if the LpcpLock is already owned by the caller
--*/
{
BOOLEAN ZoneMemoryAvailable = FALSE;
PLPCP_CONNECTION_MESSAGE ConnectMsg;
PAGED_CODE();
if (!MutexOwned) {// Take out the global lock if necessary
LpcpAcquireLpcpLock();
}
LpcpTrace(( "Free Msg %lx\n", Msg ));
#if DBG
// In the debug case if the message is not allocated then it's an error
if (!(Msg->ZoneIndex & LPCP_ZONE_MESSAGE_ALLOCATED)) {
LpcpPrint(( "Msg %lx has already been freed.\n", Msg ));
DbgBreakPoint();
if (!MutexOwned) {
LpcpReleaseLpcpLock();
}
return;
}
Msg->ZoneIndex &= ~LPCP_ZONE_MESSAGE_ALLOCATED;
#endif
// The non zero reserved0 field tells us that the message has been used
if (Msg->Reserved0 != 0) {
// A entry field connects the message to the message queue of the owning port object.
// If not already removed then remove this message
if (!IsListEmpty( &Msg->Entry )) {
RemoveEntryList( &Msg->Entry );
InitializeListHead( &Msg->Entry );
}
// If the replied to thread is not null then we have a reference to the thread that we should now remove
if (Msg->RepliedToThread != NULL) {
ObDereferenceObject( Msg->RepliedToThread );
Msg->RepliedToThread = NULL;
}
// If the msg was for a connection request then we know that right after the lpcp message is a connection message whose client port field might need to be dereferenced
if ((Msg->Request.u2.s2.Type & ~LPC_KERNELMODE_MESSAGE) == LPC_CONNECTION_REQUEST) {
ConnectMsg = (PLPCP_CONNECTION_MESSAGE)(Msg + 1);
if (ConnectMsg->ClientPort) {
PLPCP_PORT_OBJECT ClientPort;
// Capture a pointer to the client port then null it out so that no one else can use it, then release lpcp lock before we dereference the client port
ClientPort = ConnectMsg->ClientPort;
ConnectMsg->ClientPort = NULL;
LpcpReleaseLpcpLock();
ObDereferenceObject( ClientPort );
LpcpAcquireLpcpLock();
}
}
// Now mark this message a free, and return it to the zone
Msg->Reserved0 = 0;
ZoneMemoryAvailable = (BOOLEAN)(ExFreeToZone( &LpcpZone.Zone, &Msg->FreeEntry ) == NULL);
}
// Check if we need to release the global lpcp lock
if (!MutexOwned) {
LpcpReleaseLpcpLock();
}
// If we've actually freed up memory then go wake any waiting allocators
// **** does this need to check to see if it owns the lpcp lock.
// The release right above us and this test and set event should be combined
if (ZoneMemoryAvailable) {
KeSetEvent( &LpcpZone.FreeEvent, LPC_RELEASE_WAIT_INCREMENT, FALSE );
}
return;// And return to our caller
}
VOID LpcpSaveDataInfoMessage (IN PLPCP_PORT_OBJECT Port, PLPCP_MESSAGE Msg)
/*++
Routine Description:
This routine is used in place of freeing a message and instead saves the message off a seperate queue from the port.
Arguments:
Port - Specifies the port object under which to save this message
Msg - Supplies the message being saved
--*/
{
PAGED_CODE();
// Take out the global lock
LpcpAcquireLpcpLock();
// Make sure we get to the connection port object of this port
if ((Port->Flags & PORT_TYPE) > UNCONNECTED_COMMUNICATION_PORT) {
Port = Port->ConnectionPort;
}
LpcpTrace(( "%s Saving DataInfo Message %lx (%u.%u) Port: %lx\n",
PsGetCurrentProcess()->ImageFileName, Msg, Msg->Request.MessageId, Msg->Request.CallbackId, Port ));
InsertTailList( &Port->LpcDataInfoChainHead, &Msg->Entry );// Enqueue this message onto the data info chain for the port
LpcpReleaseLpcpLock();// Free the global lock
return;// And return to our caller
}
VOID LpcpFreeDataInfoMessage (IN PLPCP_PORT_OBJECT Port, IN ULONG MessageId, IN ULONG CallbackId)
/*++
Routine Description:
This routine is used to free up a saved message in a port
Arguments:
Port - Supplies the port being manipulated
MessageId - Supplies the id of the message being freed
CallbackId - Supplies the callback id of the message being freed
--*/
{
PLPCP_MESSAGE Msg;
PLIST_ENTRY Head, Next;
PAGED_CODE();
// Make sure we get to the connection port object of this port
if ((Port->Flags & PORT_TYPE) > UNCONNECTED_COMMUNICATION_PORT) {
Port = Port->ConnectionPort;
}
// Zoom down the data info chain for the connection port object
Head = &Port->LpcDataInfoChainHead;
Next = Head->Flink;
while (Next != Head) {
Msg = CONTAINING_RECORD( Next, LPCP_MESSAGE, Entry );
// If this message matches the callers specification then remove this message,
// free it back to the port zone, and return back to our caller
if ((Msg->Request.MessageId == MessageId) && (Msg->Request.CallbackId == CallbackId)) {
LpcpTrace(( "%s Removing DataInfo Message %lx (%u.%u) Port: %lx\n",
PsGetCurrentProcess()->ImageFileName, Msg, Msg->Request.MessageId, Msg->Request.CallbackId, Port ));
RemoveEntryList( &Msg->Entry );
InitializeListHead( &Msg->Entry );
LpcpFreeToPortZone( Msg, TRUE );
return;
} else {
Next = Next->Flink;// Keep on going down the data info chain
}
}
// We didn't find a match so just return to our caller
LpcpTrace(( "%s Unable to find DataInfo Message (%u.%u) Port: %lx\n",
PsGetCurrentProcess()->ImageFileName, MessageId, CallbackId, Port ));
}
PLPCP_MESSAGE LpcpFindDataInfoMessage (IN PLPCP_PORT_OBJECT Port, IN ULONG MessageId, IN ULONG CallbackId)
/*++
Routine Description:
This routine is used to locate a specific message stored off the data info chain of a port
Arguments:
Port - Supplies the port being examined
MessageId - Supplies the ID of the message being searched for
CallbackId - Supplies the callback ID being searched for
Return Value:
PLPCP_MESSAGE - returns a pointer to the message satisfying the search criteria or NULL of none was found
--*/
{
PLPCP_MESSAGE Msg;
PLIST_ENTRY Head, Next;
PAGED_CODE();
// Make sure we get to the connection port object of this port
if ((Port->Flags & PORT_TYPE) > UNCONNECTED_COMMUNICATION_PORT) {
Port = Port->ConnectionPort;
}
// Zoom down the data info chain for the connection port object looking for a match
Head = &Port->LpcDataInfoChainHead;
Next = Head->Flink;
while (Next != Head) {
Msg = CONTAINING_RECORD( Next, LPCP_MESSAGE, Entry );
if ((Msg->Request.MessageId == MessageId) && (Msg->Request.CallbackId == CallbackId)) {
LpcpTrace(( "%s Found DataInfo Message %lx (%u.%u) Port: %lx\n",
PsGetCurrentProcess()->ImageFileName,
Msg,
Msg->Request.MessageId,
Msg->Request.CallbackId,
Port ));
return Msg;
} else {
Next = Next->Flink;
}
}
// We did not find a match so return null to our caller
LpcpTrace(( "%s Unable to find DataInfo Message (%u.%u) Port: %lx\n",
PsGetCurrentProcess()->ImageFileName, MessageId, CallbackId, Port ));
return NULL;
}