mirror of
https://github.com/reactos/reactos.git
synced 2024-12-26 17:14:41 +00:00
b3c55b9e6c
CORE-18098
1050 lines
33 KiB
C
1050 lines
33 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: ntoskrnl/lpc/send.c
|
|
* PURPOSE: Local Procedure Call: Sending (Requests)
|
|
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/* PUBLIC FUNCTIONS **********************************************************/
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
LpcRequestPort(IN PVOID PortObject,
|
|
IN PPORT_MESSAGE LpcMessage)
|
|
{
|
|
PLPCP_PORT_OBJECT Port = PortObject, QueuePort, ConnectionPort = NULL;
|
|
ULONG MessageType;
|
|
PLPCP_MESSAGE Message;
|
|
KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
|
|
PETHREAD Thread = PsGetCurrentThread();
|
|
|
|
PAGED_CODE();
|
|
|
|
LPCTRACE(LPC_SEND_DEBUG, "Port: %p. Message: %p\n", Port, LpcMessage);
|
|
|
|
/* Check if this is a non-datagram message */
|
|
if (LpcMessage->u2.s2.Type)
|
|
{
|
|
/* Get the message type */
|
|
MessageType = LpcpGetMessageType(LpcMessage);
|
|
|
|
/* Validate it */
|
|
if ((MessageType < LPC_DATAGRAM) || (MessageType > LPC_CLIENT_DIED))
|
|
{
|
|
/* Fail */
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Mark this as a kernel-mode message only if we really came from it */
|
|
if ((PreviousMode == KernelMode) &&
|
|
(LpcMessage->u2.s2.Type & LPC_KERNELMODE_MESSAGE))
|
|
{
|
|
/* We did, this is a kernel mode message */
|
|
MessageType |= LPC_KERNELMODE_MESSAGE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* This is a datagram */
|
|
MessageType = LPC_DATAGRAM;
|
|
}
|
|
|
|
/* Can't have data information on this type of call */
|
|
if (LpcMessage->u2.s2.DataInfoOffset) return STATUS_INVALID_PARAMETER;
|
|
|
|
/* Validate the message length */
|
|
if (((ULONG)LpcMessage->u1.s1.TotalLength > Port->MaxMessageLength) ||
|
|
((ULONG)LpcMessage->u1.s1.TotalLength <= (ULONG)LpcMessage->u1.s1.DataLength))
|
|
{
|
|
/* Fail */
|
|
return STATUS_PORT_MESSAGE_TOO_LONG;
|
|
}
|
|
|
|
/* Allocate a new message */
|
|
Message = LpcpAllocateFromPortZone();
|
|
if (!Message) return STATUS_NO_MEMORY;
|
|
|
|
/* Clear the context */
|
|
Message->RepliedToThread = NULL;
|
|
Message->PortContext = NULL;
|
|
|
|
/* Copy the message */
|
|
LpcpMoveMessage(&Message->Request,
|
|
LpcMessage,
|
|
LpcMessage + 1,
|
|
MessageType,
|
|
&Thread->Cid);
|
|
|
|
/* Acquire the LPC lock */
|
|
KeAcquireGuardedMutex(&LpcpLock);
|
|
|
|
/* Check if this is anything but a connection port */
|
|
if ((Port->Flags & LPCP_PORT_TYPE_MASK) != LPCP_CONNECTION_PORT)
|
|
{
|
|
/* The queue port is the connected port */
|
|
QueuePort = Port->ConnectedPort;
|
|
if (QueuePort)
|
|
{
|
|
/* Check if this is a client port */
|
|
if ((Port->Flags & LPCP_PORT_TYPE_MASK) == LPCP_CLIENT_PORT)
|
|
{
|
|
/* Then copy the context */
|
|
Message->PortContext = QueuePort->PortContext;
|
|
ConnectionPort = QueuePort = Port->ConnectionPort;
|
|
if (!ConnectionPort)
|
|
{
|
|
/* Fail */
|
|
LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
return STATUS_PORT_DISCONNECTED;
|
|
}
|
|
}
|
|
else if ((Port->Flags & LPCP_PORT_TYPE_MASK) != LPCP_COMMUNICATION_PORT)
|
|
{
|
|
/* Any other kind of port, use the connection port */
|
|
ConnectionPort = QueuePort = Port->ConnectionPort;
|
|
if (!ConnectionPort)
|
|
{
|
|
/* Fail */
|
|
LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
return STATUS_PORT_DISCONNECTED;
|
|
}
|
|
}
|
|
|
|
/* If we have a connection port, reference it */
|
|
if (ConnectionPort) ObReferenceObject(ConnectionPort);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* For connection ports, use the port itself */
|
|
QueuePort = PortObject;
|
|
}
|
|
|
|
/* Make sure we have a port */
|
|
if (QueuePort)
|
|
{
|
|
/* Generate the Message ID and set it */
|
|
Message->Request.MessageId = LpcpNextMessageId++;
|
|
if (!LpcpNextMessageId) LpcpNextMessageId = 1;
|
|
Message->Request.CallbackId = 0;
|
|
|
|
/* No Message ID for the thread */
|
|
Thread->LpcReplyMessageId = 0;
|
|
|
|
/* Insert the message in our chain */
|
|
InsertTailList(&QueuePort->MsgQueue.ReceiveHead, &Message->Entry);
|
|
|
|
/* Release the lock and the semaphore */
|
|
KeEnterCriticalRegion();
|
|
KeReleaseGuardedMutex(&LpcpLock);
|
|
LpcpCompleteWait(QueuePort->MsgQueue.Semaphore);
|
|
|
|
/* If this is a waitable port, wake it up */
|
|
if (QueuePort->Flags & LPCP_WAITABLE_PORT)
|
|
{
|
|
/* Wake it */
|
|
KeSetEvent(&QueuePort->WaitEvent, IO_NO_INCREMENT, FALSE);
|
|
}
|
|
|
|
KeLeaveCriticalRegion();
|
|
|
|
/* We're done */
|
|
if (ConnectionPort) ObDereferenceObject(ConnectionPort);
|
|
LPCTRACE(LPC_SEND_DEBUG, "Port: %p. Message: %p\n", QueuePort, Message);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* If we got here, then free the message and fail */
|
|
LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
if (ConnectionPort) ObDereferenceObject(ConnectionPort);
|
|
return STATUS_PORT_DISCONNECTED;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
LpcRequestWaitReplyPort(IN PVOID PortObject,
|
|
IN PPORT_MESSAGE LpcRequest,
|
|
OUT PPORT_MESSAGE LpcReply)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
|
|
PETHREAD Thread = PsGetCurrentThread();
|
|
PLPCP_PORT_OBJECT Port = (PLPCP_PORT_OBJECT)PortObject;
|
|
PLPCP_PORT_OBJECT QueuePort, ReplyPort, ConnectionPort = NULL;
|
|
USHORT MessageType;
|
|
PLPCP_MESSAGE Message;
|
|
BOOLEAN Callback = FALSE;
|
|
PKSEMAPHORE Semaphore;
|
|
|
|
PAGED_CODE();
|
|
|
|
LPCTRACE(LPC_SEND_DEBUG,
|
|
"Port: %p. Messages: %p/%p. Type: %lx\n",
|
|
Port,
|
|
LpcRequest,
|
|
LpcReply,
|
|
LpcpGetMessageType(LpcRequest));
|
|
|
|
/* Check if the thread is dying */
|
|
if (Thread->LpcExitThreadCalled) return STATUS_THREAD_IS_TERMINATING;
|
|
|
|
/* Check if this is an LPC Request */
|
|
MessageType = LpcpGetMessageType(LpcRequest);
|
|
switch (MessageType)
|
|
{
|
|
/* No type, assume LPC request */
|
|
case 0:
|
|
MessageType = LPC_REQUEST;
|
|
break;
|
|
|
|
/* LPC request callback */
|
|
case LPC_REQUEST:
|
|
Callback = TRUE;
|
|
break;
|
|
|
|
/* Anything else, nothing to do */
|
|
case LPC_CLIENT_DIED:
|
|
case LPC_PORT_CLOSED:
|
|
case LPC_EXCEPTION:
|
|
case LPC_DEBUG_EVENT:
|
|
case LPC_ERROR_EVENT:
|
|
break;
|
|
|
|
/* Invalid message type */
|
|
default:
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Set the request type */
|
|
LpcRequest->u2.s2.Type = MessageType;
|
|
|
|
/* Validate the message length */
|
|
if (((ULONG)LpcRequest->u1.s1.TotalLength > Port->MaxMessageLength) ||
|
|
((ULONG)LpcRequest->u1.s1.TotalLength <= (ULONG)LpcRequest->u1.s1.DataLength))
|
|
{
|
|
/* Fail */
|
|
return STATUS_PORT_MESSAGE_TOO_LONG;
|
|
}
|
|
|
|
/* Allocate a message from the port zone */
|
|
Message = LpcpAllocateFromPortZone();
|
|
if (!Message)
|
|
{
|
|
/* Fail if we couldn't allocate a message */
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
/* Check if this is a callback */
|
|
if (Callback)
|
|
{
|
|
/* FIXME: TODO */
|
|
Semaphore = NULL; // we'd use the Thread Semaphore here
|
|
ASSERT(FALSE);
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
else
|
|
{
|
|
/* No callback, just copy the message */
|
|
LpcpMoveMessage(&Message->Request,
|
|
LpcRequest,
|
|
LpcRequest + 1,
|
|
0,
|
|
&Thread->Cid);
|
|
|
|
/* Acquire the LPC lock */
|
|
KeAcquireGuardedMutex(&LpcpLock);
|
|
|
|
/* Right now clear the port context */
|
|
Message->PortContext = NULL;
|
|
|
|
/* Check if this is a not connection port */
|
|
if ((Port->Flags & LPCP_PORT_TYPE_MASK) != LPCP_CONNECTION_PORT)
|
|
{
|
|
/* We want the connected port */
|
|
QueuePort = Port->ConnectedPort;
|
|
if (!QueuePort)
|
|
{
|
|
/* We have no connected port, fail */
|
|
LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
return STATUS_PORT_DISCONNECTED;
|
|
}
|
|
|
|
/* This will be the rundown port */
|
|
ReplyPort = QueuePort;
|
|
|
|
/* Check if this is a communication port */
|
|
if ((Port->Flags & LPCP_PORT_TYPE_MASK) == LPCP_CLIENT_PORT)
|
|
{
|
|
/* Copy the port context and use the connection port */
|
|
Message->PortContext = QueuePort->PortContext;
|
|
ConnectionPort = QueuePort = Port->ConnectionPort;
|
|
if (!ConnectionPort)
|
|
{
|
|
/* Fail */
|
|
LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
return STATUS_PORT_DISCONNECTED;
|
|
}
|
|
}
|
|
else if ((Port->Flags & LPCP_PORT_TYPE_MASK) !=
|
|
LPCP_COMMUNICATION_PORT)
|
|
{
|
|
/* Use the connection port for anything but communication ports */
|
|
ConnectionPort = QueuePort = Port->ConnectionPort;
|
|
if (!ConnectionPort)
|
|
{
|
|
/* Fail */
|
|
LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
return STATUS_PORT_DISCONNECTED;
|
|
}
|
|
}
|
|
|
|
/* Reference the connection port if it exists */
|
|
if (ConnectionPort) ObReferenceObject(ConnectionPort);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, for a connection port, use the same port object */
|
|
QueuePort = ReplyPort = Port;
|
|
}
|
|
|
|
/* No reply thread */
|
|
Message->RepliedToThread = NULL;
|
|
Message->SenderPort = Port;
|
|
|
|
/* Generate the Message ID and set it */
|
|
Message->Request.MessageId = LpcpNextMessageId++;
|
|
if (!LpcpNextMessageId) LpcpNextMessageId = 1;
|
|
Message->Request.CallbackId = 0;
|
|
|
|
/* Set the message ID for our thread now */
|
|
Thread->LpcReplyMessageId = Message->Request.MessageId;
|
|
Thread->LpcReplyMessage = NULL;
|
|
|
|
/* Insert the message in our chain */
|
|
InsertTailList(&QueuePort->MsgQueue.ReceiveHead, &Message->Entry);
|
|
InsertTailList(&ReplyPort->LpcReplyChainHead, &Thread->LpcReplyChain);
|
|
LpcpSetPortToThread(Thread, Port);
|
|
|
|
/* Release the lock and get the semaphore we'll use later */
|
|
KeEnterCriticalRegion();
|
|
KeReleaseGuardedMutex(&LpcpLock);
|
|
Semaphore = QueuePort->MsgQueue.Semaphore;
|
|
|
|
/* If this is a waitable port, wake it up */
|
|
if (QueuePort->Flags & LPCP_WAITABLE_PORT)
|
|
{
|
|
/* Wake it */
|
|
KeSetEvent(&QueuePort->WaitEvent, IO_NO_INCREMENT, FALSE);
|
|
}
|
|
}
|
|
|
|
/* Now release the semaphore */
|
|
LpcpCompleteWait(Semaphore);
|
|
KeLeaveCriticalRegion();
|
|
|
|
/* And let's wait for the reply */
|
|
LpcpReplyWait(&Thread->LpcReplySemaphore, PreviousMode);
|
|
|
|
/* Acquire the LPC lock */
|
|
KeAcquireGuardedMutex(&LpcpLock);
|
|
|
|
/* Get the LPC Message and clear our thread's reply data */
|
|
Message = LpcpGetMessageFromThread(Thread);
|
|
Thread->LpcReplyMessage = NULL;
|
|
Thread->LpcReplyMessageId = 0;
|
|
|
|
/* Check if we have anything on the reply chain*/
|
|
if (!IsListEmpty(&Thread->LpcReplyChain))
|
|
{
|
|
/* Remove this thread and reinitialize the list */
|
|
RemoveEntryList(&Thread->LpcReplyChain);
|
|
InitializeListHead(&Thread->LpcReplyChain);
|
|
}
|
|
|
|
/* Release the lock */
|
|
KeReleaseGuardedMutex(&LpcpLock);
|
|
|
|
/* Check if we got a reply */
|
|
if (Status == STATUS_SUCCESS)
|
|
{
|
|
/* Check if we have a valid message */
|
|
if (Message)
|
|
{
|
|
LPCTRACE(LPC_SEND_DEBUG,
|
|
"Reply Messages: %p/%p\n",
|
|
&Message->Request,
|
|
(&Message->Request) + 1);
|
|
|
|
/* Move the message */
|
|
LpcpMoveMessage(LpcReply,
|
|
&Message->Request,
|
|
(&Message->Request) + 1,
|
|
0,
|
|
NULL);
|
|
|
|
/* Acquire the lock */
|
|
KeAcquireGuardedMutex(&LpcpLock);
|
|
|
|
/* Check if we replied to a thread */
|
|
if (Message->RepliedToThread)
|
|
{
|
|
/* Dereference */
|
|
ObDereferenceObject(Message->RepliedToThread);
|
|
Message->RepliedToThread = NULL;
|
|
}
|
|
|
|
/* Free the message */
|
|
LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
}
|
|
else
|
|
{
|
|
/* We don't have a reply */
|
|
Status = STATUS_LPC_REPLY_LOST;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* The wait failed, free the message */
|
|
if (Message) LpcpFreeToPortZone(Message, 0);
|
|
}
|
|
|
|
/* All done */
|
|
LPCTRACE(LPC_SEND_DEBUG,
|
|
"Port: %p. Status: %d\n",
|
|
Port,
|
|
Status);
|
|
|
|
/* Dereference the connection port */
|
|
if (ConnectionPort) ObDereferenceObject(ConnectionPort);
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
NtRequestPort(IN HANDLE PortHandle,
|
|
IN PPORT_MESSAGE LpcRequest)
|
|
{
|
|
NTSTATUS Status;
|
|
KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
|
|
PETHREAD Thread = PsGetCurrentThread();
|
|
PORT_MESSAGE CapturedLpcRequest;
|
|
PLPCP_PORT_OBJECT Port, QueuePort, ConnectionPort = NULL;
|
|
ULONG MessageType;
|
|
PLPCP_MESSAGE Message;
|
|
|
|
PAGED_CODE();
|
|
|
|
/* Check if the call comes from user mode */
|
|
if (PreviousMode != KernelMode)
|
|
{
|
|
_SEH2_TRY
|
|
{
|
|
/* Probe and capture the LpcRequest */
|
|
ProbeForRead(LpcRequest, sizeof(*LpcRequest), sizeof(ULONG));
|
|
CapturedLpcRequest = *(volatile PORT_MESSAGE*)LpcRequest;
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
else
|
|
{
|
|
/* Access the LpcRequest directly */
|
|
CapturedLpcRequest = *LpcRequest;
|
|
}
|
|
|
|
LPCTRACE(LPC_SEND_DEBUG,
|
|
"Handle: %p. Message: %p. Type: %lx\n",
|
|
PortHandle,
|
|
LpcRequest,
|
|
LpcpGetMessageType(&CapturedLpcRequest));
|
|
|
|
/* Get the message type */
|
|
MessageType = CapturedLpcRequest.u2.s2.Type | LPC_DATAGRAM;
|
|
|
|
/* Can't have data information on this type of call */
|
|
if (CapturedLpcRequest.u2.s2.DataInfoOffset) return STATUS_INVALID_PARAMETER;
|
|
|
|
/* Validate the length */
|
|
if (((ULONG)CapturedLpcRequest.u1.s1.DataLength + sizeof(PORT_MESSAGE)) >
|
|
(ULONG)CapturedLpcRequest.u1.s1.TotalLength)
|
|
{
|
|
/* Fail */
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Reference the object */
|
|
Status = ObReferenceObjectByHandle(PortHandle,
|
|
0,
|
|
LpcPortObjectType,
|
|
PreviousMode,
|
|
(PVOID*)&Port,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
|
|
/* Validate the message length */
|
|
if (((ULONG)CapturedLpcRequest.u1.s1.TotalLength > Port->MaxMessageLength) ||
|
|
((ULONG)CapturedLpcRequest.u1.s1.TotalLength <= (ULONG)CapturedLpcRequest.u1.s1.DataLength))
|
|
{
|
|
/* Fail */
|
|
ObDereferenceObject(Port);
|
|
return STATUS_PORT_MESSAGE_TOO_LONG;
|
|
}
|
|
|
|
/* Allocate a message from the port zone */
|
|
Message = LpcpAllocateFromPortZone();
|
|
if (!Message)
|
|
{
|
|
/* Fail if we couldn't allocate a message */
|
|
ObDereferenceObject(Port);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
/* No callback, just copy the message */
|
|
_SEH2_TRY
|
|
{
|
|
/* Copy it */
|
|
LpcpMoveMessage(&Message->Request,
|
|
&CapturedLpcRequest,
|
|
LpcRequest + 1,
|
|
MessageType,
|
|
&Thread->Cid);
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Cleanup and return the exception code */
|
|
LpcpFreeToPortZone(Message, 0);
|
|
ObDereferenceObject(Port);
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
|
|
/* Acquire the LPC lock */
|
|
KeAcquireGuardedMutex(&LpcpLock);
|
|
|
|
/* Right now clear the port context */
|
|
Message->PortContext = NULL;
|
|
|
|
/* Check if this is a not connection port */
|
|
if ((Port->Flags & LPCP_PORT_TYPE_MASK) != LPCP_CONNECTION_PORT)
|
|
{
|
|
/* We want the connected port */
|
|
QueuePort = Port->ConnectedPort;
|
|
if (!QueuePort)
|
|
{
|
|
/* We have no connected port, fail */
|
|
LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
ObDereferenceObject(Port);
|
|
return STATUS_PORT_DISCONNECTED;
|
|
}
|
|
|
|
/* Check if this is a communication port */
|
|
if ((Port->Flags & LPCP_PORT_TYPE_MASK) == LPCP_CLIENT_PORT)
|
|
{
|
|
/* Copy the port context and use the connection port */
|
|
Message->PortContext = QueuePort->PortContext;
|
|
ConnectionPort = QueuePort = Port->ConnectionPort;
|
|
if (!ConnectionPort)
|
|
{
|
|
/* Fail */
|
|
LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
ObDereferenceObject(Port);
|
|
return STATUS_PORT_DISCONNECTED;
|
|
}
|
|
}
|
|
else if ((Port->Flags & LPCP_PORT_TYPE_MASK) != LPCP_COMMUNICATION_PORT)
|
|
{
|
|
/* Use the connection port for anything but communication ports */
|
|
ConnectionPort = QueuePort = Port->ConnectionPort;
|
|
if (!ConnectionPort)
|
|
{
|
|
/* Fail */
|
|
LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
ObDereferenceObject(Port);
|
|
return STATUS_PORT_DISCONNECTED;
|
|
}
|
|
}
|
|
|
|
/* Reference the connection port if it exists */
|
|
if (ConnectionPort) ObReferenceObject(ConnectionPort);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, for a connection port, use the same port object */
|
|
QueuePort = Port;
|
|
}
|
|
|
|
/* Reference QueuePort if we have it */
|
|
if (QueuePort && ObReferenceObjectSafe(QueuePort))
|
|
{
|
|
/* Set sender's port */
|
|
Message->SenderPort = Port;
|
|
|
|
/* Generate the Message ID and set it */
|
|
Message->Request.MessageId = LpcpNextMessageId++;
|
|
if (!LpcpNextMessageId) LpcpNextMessageId = 1;
|
|
Message->Request.CallbackId = 0;
|
|
|
|
/* No Message ID for the thread */
|
|
Thread->LpcReplyMessageId = 0;
|
|
|
|
/* Insert the message in our chain */
|
|
InsertTailList(&QueuePort->MsgQueue.ReceiveHead, &Message->Entry);
|
|
|
|
/* Release the lock and the semaphore */
|
|
KeEnterCriticalRegion();
|
|
KeReleaseGuardedMutex(&LpcpLock);
|
|
LpcpCompleteWait(QueuePort->MsgQueue.Semaphore);
|
|
|
|
/* If this is a waitable port, wake it up */
|
|
if (QueuePort->Flags & LPCP_WAITABLE_PORT)
|
|
{
|
|
/* Wake it */
|
|
KeSetEvent(&QueuePort->WaitEvent, IO_NO_INCREMENT, FALSE);
|
|
}
|
|
|
|
KeLeaveCriticalRegion();
|
|
|
|
/* Dereference objects */
|
|
if (ConnectionPort) ObDereferenceObject(ConnectionPort);
|
|
ObDereferenceObject(QueuePort);
|
|
ObDereferenceObject(Port);
|
|
LPCTRACE(LPC_SEND_DEBUG, "Port: %p. Message: %p\n", QueuePort, Message);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
Status = STATUS_PORT_DISCONNECTED;
|
|
|
|
/* All done with a failure*/
|
|
LPCTRACE(LPC_SEND_DEBUG,
|
|
"Port: %p. Status: %d\n",
|
|
Port,
|
|
Status);
|
|
|
|
/* The wait failed, free the message */
|
|
if (Message) LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
|
|
ObDereferenceObject(Port);
|
|
if (ConnectionPort) ObDereferenceObject(ConnectionPort);
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
LpcpVerifyMessageDataInfo(
|
|
_In_ PPORT_MESSAGE Message,
|
|
_Out_ PULONG NumberOfDataEntries)
|
|
{
|
|
PLPCP_DATA_INFO DataInfo;
|
|
PUCHAR EndOfEntries;
|
|
|
|
/* Check if we have no data info at all */
|
|
if (Message->u2.s2.DataInfoOffset == 0)
|
|
{
|
|
*NumberOfDataEntries = 0;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* Make sure the data info structure is within the message */
|
|
if (((ULONG)Message->u1.s1.TotalLength <
|
|
sizeof(PORT_MESSAGE) + sizeof(LPCP_DATA_INFO)) ||
|
|
((ULONG)Message->u2.s2.DataInfoOffset < sizeof(PORT_MESSAGE)) ||
|
|
((ULONG)Message->u2.s2.DataInfoOffset >
|
|
((ULONG)Message->u1.s1.TotalLength - sizeof(LPCP_DATA_INFO))))
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Get a pointer to the data info */
|
|
DataInfo = LpcpGetDataInfoFromMessage(Message);
|
|
|
|
/* Make sure the full data info with all entries is within the message */
|
|
EndOfEntries = (PUCHAR)&DataInfo->Entries[DataInfo->NumberOfEntries];
|
|
if ((EndOfEntries > ((PUCHAR)Message + (ULONG)Message->u1.s1.TotalLength)) ||
|
|
(EndOfEntries < (PUCHAR)Message))
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
*NumberOfDataEntries = DataInfo->NumberOfEntries;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
NtRequestWaitReplyPort(IN HANDLE PortHandle,
|
|
IN PPORT_MESSAGE LpcRequest,
|
|
IN OUT PPORT_MESSAGE LpcReply)
|
|
{
|
|
NTSTATUS Status;
|
|
PORT_MESSAGE CapturedLpcRequest;
|
|
ULONG NumberOfDataEntries;
|
|
PLPCP_PORT_OBJECT Port, QueuePort, ReplyPort, ConnectionPort = NULL;
|
|
PLPCP_MESSAGE Message;
|
|
KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
|
|
PETHREAD Thread = PsGetCurrentThread();
|
|
BOOLEAN Callback;
|
|
PKSEMAPHORE Semaphore;
|
|
ULONG MessageType;
|
|
PLPCP_DATA_INFO DataInfo;
|
|
|
|
PAGED_CODE();
|
|
|
|
/* Check if the thread is dying */
|
|
if (Thread->LpcExitThreadCalled)
|
|
return STATUS_THREAD_IS_TERMINATING;
|
|
|
|
/* Check for user mode access */
|
|
if (PreviousMode != KernelMode)
|
|
{
|
|
_SEH2_TRY
|
|
{
|
|
/* Probe and capture the LpcRequest */
|
|
ProbeForRead(LpcRequest, sizeof(*LpcRequest), sizeof(ULONG));
|
|
CapturedLpcRequest = *(volatile PORT_MESSAGE*)LpcRequest;
|
|
|
|
/* Probe the reply message for write */
|
|
ProbeForWrite(LpcReply, sizeof(*LpcReply), sizeof(ULONG));
|
|
|
|
/* Make sure the data entries in the request message are valid */
|
|
Status = LpcpVerifyMessageDataInfo(LpcRequest, &NumberOfDataEntries);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("LpcpVerifyMessageDataInfo failed\n");
|
|
_SEH2_YIELD(return Status);
|
|
}
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
DPRINT1("Got exception\n");
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
else
|
|
{
|
|
CapturedLpcRequest = *LpcRequest;
|
|
Status = LpcpVerifyMessageDataInfo(LpcRequest, &NumberOfDataEntries);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("LpcpVerifyMessageDataInfo failed\n");
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
LPCTRACE(LPC_SEND_DEBUG,
|
|
"Handle: %p. Messages: %p/%p. Type: %lx\n",
|
|
PortHandle,
|
|
LpcRequest,
|
|
LpcReply,
|
|
LpcpGetMessageType(&CapturedLpcRequest));
|
|
|
|
/* This flag is undocumented. Remove it before continuing */
|
|
CapturedLpcRequest.u2.s2.Type &= ~0x4000;
|
|
|
|
/* Check if this is an LPC Request */
|
|
if (LpcpGetMessageType(&CapturedLpcRequest) == LPC_REQUEST)
|
|
{
|
|
/* Then it's a callback */
|
|
Callback = TRUE;
|
|
}
|
|
else if (LpcpGetMessageType(&CapturedLpcRequest))
|
|
{
|
|
/* This is a not kernel-mode message */
|
|
DPRINT1("Not a kernel-mode message!\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
else
|
|
{
|
|
/* This is a kernel-mode message without a callback */
|
|
CapturedLpcRequest.u2.s2.Type |= LPC_REQUEST;
|
|
Callback = FALSE;
|
|
}
|
|
|
|
/* Get the message type */
|
|
MessageType = CapturedLpcRequest.u2.s2.Type;
|
|
|
|
/* Due to the above probe, we know that TotalLength is positive */
|
|
ASSERT(CapturedLpcRequest.u1.s1.TotalLength >= 0);
|
|
|
|
/* Validate the length */
|
|
if ((((ULONG)(USHORT)CapturedLpcRequest.u1.s1.DataLength + sizeof(PORT_MESSAGE)) >
|
|
(ULONG)CapturedLpcRequest.u1.s1.TotalLength))
|
|
{
|
|
/* Fail */
|
|
DPRINT1("Invalid message length: %u, %u\n",
|
|
CapturedLpcRequest.u1.s1.DataLength,
|
|
CapturedLpcRequest.u1.s1.TotalLength);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Reference the object */
|
|
Status = ObReferenceObjectByHandle(PortHandle,
|
|
0,
|
|
LpcPortObjectType,
|
|
PreviousMode,
|
|
(PVOID*)&Port,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
|
|
/* Validate the message length */
|
|
if (((ULONG)CapturedLpcRequest.u1.s1.TotalLength > Port->MaxMessageLength) ||
|
|
((ULONG)CapturedLpcRequest.u1.s1.TotalLength <= (ULONG)CapturedLpcRequest.u1.s1.DataLength))
|
|
{
|
|
/* Fail */
|
|
DPRINT1("Invalid message length: %u, %u\n",
|
|
CapturedLpcRequest.u1.s1.DataLength,
|
|
CapturedLpcRequest.u1.s1.TotalLength);
|
|
ObDereferenceObject(Port);
|
|
return STATUS_PORT_MESSAGE_TOO_LONG;
|
|
}
|
|
|
|
/* Allocate a message from the port zone */
|
|
Message = LpcpAllocateFromPortZone();
|
|
if (!Message)
|
|
{
|
|
/* Fail if we couldn't allocate a message */
|
|
DPRINT1("Failed to allocate a message!\n");
|
|
ObDereferenceObject(Port);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
/* Check if this is a callback */
|
|
if (Callback)
|
|
{
|
|
/* FIXME: TODO */
|
|
Semaphore = NULL; // we'd use the Thread Semaphore here
|
|
ASSERT(FALSE);
|
|
}
|
|
else
|
|
{
|
|
/* No callback, just copy the message */
|
|
_SEH2_TRY
|
|
{
|
|
/* Check if we have data info entries */
|
|
if (LpcRequest->u2.s2.DataInfoOffset != 0)
|
|
{
|
|
/* Get the data info and check if the number of entries matches
|
|
what we expect */
|
|
DataInfo = LpcpGetDataInfoFromMessage(LpcRequest);
|
|
if (DataInfo->NumberOfEntries != NumberOfDataEntries)
|
|
{
|
|
LpcpFreeToPortZone(Message, 0);
|
|
ObDereferenceObject(Port);
|
|
DPRINT1("NumberOfEntries has changed: %u, %u\n",
|
|
DataInfo->NumberOfEntries, NumberOfDataEntries);
|
|
_SEH2_YIELD(return STATUS_INVALID_PARAMETER);
|
|
}
|
|
}
|
|
|
|
/* Copy it */
|
|
LpcpMoveMessage(&Message->Request,
|
|
&CapturedLpcRequest,
|
|
LpcRequest + 1,
|
|
MessageType,
|
|
&Thread->Cid);
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Cleanup and return the exception code */
|
|
DPRINT1("Got exception!\n");
|
|
LpcpFreeToPortZone(Message, 0);
|
|
ObDereferenceObject(Port);
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
|
|
/* Acquire the LPC lock */
|
|
KeAcquireGuardedMutex(&LpcpLock);
|
|
|
|
/* Right now clear the port context */
|
|
Message->PortContext = NULL;
|
|
|
|
/* Check if this is a not connection port */
|
|
if ((Port->Flags & LPCP_PORT_TYPE_MASK) != LPCP_CONNECTION_PORT)
|
|
{
|
|
/* We want the connected port */
|
|
QueuePort = Port->ConnectedPort;
|
|
if (!QueuePort)
|
|
{
|
|
/* We have no connected port, fail */
|
|
DPRINT1("No connected port\n");
|
|
LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
ObDereferenceObject(Port);
|
|
return STATUS_PORT_DISCONNECTED;
|
|
}
|
|
|
|
/* This will be the rundown port */
|
|
ReplyPort = QueuePort;
|
|
|
|
/* Check if this is a client port */
|
|
if ((Port->Flags & LPCP_PORT_TYPE_MASK) == LPCP_CLIENT_PORT)
|
|
{
|
|
/* Copy the port context */
|
|
Message->PortContext = QueuePort->PortContext;
|
|
}
|
|
|
|
if ((Port->Flags & LPCP_PORT_TYPE_MASK) != LPCP_COMMUNICATION_PORT)
|
|
{
|
|
/* Use the connection port for anything but communication ports */
|
|
ConnectionPort = QueuePort = Port->ConnectionPort;
|
|
if (!ConnectionPort)
|
|
{
|
|
/* Fail */
|
|
DPRINT1("No connection port\n");
|
|
LpcpFreeToPortZone(Message, LPCP_LOCK_HELD | LPCP_LOCK_RELEASE);
|
|
ObDereferenceObject(Port);
|
|
return STATUS_PORT_DISCONNECTED;
|
|
}
|
|
}
|
|
|
|
/* Reference the connection port if it exists */
|
|
if (ConnectionPort) ObReferenceObject(ConnectionPort);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, for a connection port, use the same port object */
|
|
QueuePort = ReplyPort = Port;
|
|
}
|
|
|
|
/* No reply thread */
|
|
Message->RepliedToThread = NULL;
|
|
Message->SenderPort = Port;
|
|
|
|
/* Generate the Message ID and set it */
|
|
Message->Request.MessageId = LpcpNextMessageId++;
|
|
if (!LpcpNextMessageId) LpcpNextMessageId = 1;
|
|
Message->Request.CallbackId = 0;
|
|
|
|
/* Set the message ID for our thread now */
|
|
Thread->LpcReplyMessageId = Message->Request.MessageId;
|
|
Thread->LpcReplyMessage = NULL;
|
|
|
|
/* Insert the message in our chain */
|
|
InsertTailList(&QueuePort->MsgQueue.ReceiveHead, &Message->Entry);
|
|
InsertTailList(&ReplyPort->LpcReplyChainHead, &Thread->LpcReplyChain);
|
|
LpcpSetPortToThread(Thread, Port);
|
|
|
|
/* Release the lock and get the semaphore we'll use later */
|
|
KeEnterCriticalRegion();
|
|
KeReleaseGuardedMutex(&LpcpLock);
|
|
Semaphore = QueuePort->MsgQueue.Semaphore;
|
|
|
|
/* If this is a waitable port, wake it up */
|
|
if (QueuePort->Flags & LPCP_WAITABLE_PORT)
|
|
{
|
|
/* Wake it */
|
|
KeSetEvent(&QueuePort->WaitEvent, IO_NO_INCREMENT, FALSE);
|
|
}
|
|
}
|
|
|
|
/* Now release the semaphore */
|
|
LpcpCompleteWait(Semaphore);
|
|
KeLeaveCriticalRegion();
|
|
|
|
/* And let's wait for the reply */
|
|
LpcpReplyWait(&Thread->LpcReplySemaphore, PreviousMode);
|
|
|
|
/* Acquire the LPC lock */
|
|
KeAcquireGuardedMutex(&LpcpLock);
|
|
|
|
/* Get the LPC Message and clear our thread's reply data */
|
|
Message = LpcpGetMessageFromThread(Thread);
|
|
Thread->LpcReplyMessage = NULL;
|
|
Thread->LpcReplyMessageId = 0;
|
|
|
|
/* Check if we have anything on the reply chain*/
|
|
if (!IsListEmpty(&Thread->LpcReplyChain))
|
|
{
|
|
/* Remove this thread and reinitialize the list */
|
|
RemoveEntryList(&Thread->LpcReplyChain);
|
|
InitializeListHead(&Thread->LpcReplyChain);
|
|
}
|
|
|
|
/* Release the lock */
|
|
KeReleaseGuardedMutex(&LpcpLock);
|
|
|
|
/* Check if we got a reply */
|
|
if (Status == STATUS_SUCCESS)
|
|
{
|
|
/* Check if we have a valid message */
|
|
if (Message)
|
|
{
|
|
LPCTRACE(LPC_SEND_DEBUG,
|
|
"Reply Messages: %p/%p\n",
|
|
&Message->Request,
|
|
(&Message->Request) + 1);
|
|
|
|
/* Move the message */
|
|
_SEH2_TRY
|
|
{
|
|
LpcpMoveMessage(LpcReply,
|
|
&Message->Request,
|
|
(&Message->Request) + 1,
|
|
0,
|
|
NULL);
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
DPRINT1("Got exception!\n");
|
|
Status = _SEH2_GetExceptionCode();
|
|
}
|
|
_SEH2_END;
|
|
|
|
/* Check if this is an LPC request with data information */
|
|
if ((LpcpGetMessageType(&Message->Request) == LPC_REQUEST) &&
|
|
(Message->Request.u2.s2.DataInfoOffset))
|
|
{
|
|
/* Save the data information */
|
|
LpcpSaveDataInfoMessage(Port, Message, 0);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, just free it */
|
|
LpcpFreeToPortZone(Message, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* We don't have a reply */
|
|
Status = STATUS_LPC_REPLY_LOST;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* The wait failed, free the message */
|
|
if (Message) LpcpFreeToPortZone(Message, 0);
|
|
}
|
|
|
|
/* All done */
|
|
LPCTRACE(LPC_SEND_DEBUG,
|
|
"Port: %p. Status: %d\n",
|
|
Port,
|
|
Status);
|
|
ObDereferenceObject(Port);
|
|
if (ConnectionPort) ObDereferenceObject(ConnectionPort);
|
|
return Status;
|
|
}
|
|
|
|
/* EOF */
|