windows-nt/Source/XPSP1/NT/base/wmi/dll/notify.c

2325 lines
76 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1997-1999 Microsoft Corporation
Module Name:
notify.c
Abstract:
Handles incoming notifications and requests for consumers and providers
Author:
16-Jan-1997 AlanWar
Revision History:
--*/
#include "wmiump.h"
#include "evntrace.h"
#include "ntcsrdll.h"
//
// These globals are essentially parameters passed from the thread crating
// the event pump. The creating thread will alloc all of these resources
// so that it can know whether the pump thread will be successful or not.
// If we ever wanted to be able to have multiple pump threads then we'd
// need to move the globals into a structure and pass the struructure to the
// pump thread.
//
HANDLE WmipEventDeviceHandle;
HANDLE WmipPumpCommandEvent;
HANDLE WmipMyProcessHandle;
OVERLAPPED WmipOverlapped1, WmipOverlapped2;
PUCHAR WmipEventBuffer1, WmipEventBuffer2;
ULONG WmipEventBufferSize1, WmipEventBufferSize2;
//
// How long to wait before the event pump thread times out. On checked
// builds we want to stress the event pump and so we timeout almost
// right away. On free builds we want to be more cautious so we timeout
// after 5 minutes.
//
#if DBG
#define EVENT_NOTIFICATION_WAIT 1
#else
#define EVENT_NOTIFICATION_WAIT (5 * 60 * 1000)
#endif
ULONG WmipEventNotificationWait = EVENT_NOTIFICATION_WAIT;
typedef enum
{
EVENT_PUMP_ZERO, // Pump thread has not been started yet
EVENT_PUMP_IDLE, // Pump thread was started, but then exited
EVENT_PUMP_RUNNING, // Pump thread is running
EVENT_PUMP_STOPPING // Pump thread is in process of stopping
} EVENTPUMPSTATE, *PEVENTPUMPSTATE;
EVENTPUMPSTATE WmipPumpState = EVENT_PUMP_ZERO;
BOOLEAN WmipNewPumpThreadPending;
#define WmipSendPumpCommand() SetEvent(WmipPumpCommandEvent);
#define WmipIsPumpStopping() \
((WmipPumpState == EVENT_PUMP_STOPPING) ? TRUE : FALSE)
ULONG WmipEventPumpFromKernel(
PVOID Param
);
void WmipExternalNotification(
NOTIFICATIONCALLBACK Callback,
ULONG_PTR Context,
PWNODE_HEADER Wnode
)
/*++
Routine Description:
This routine dispatches an event to the appropriate callback
routine. This process only receives events from the WMI service that
need to be dispatched within this process. The callback address for the
specific event is passed by the wmi service in Wnode->Linkage.
Arguments:
Callback is address to callback
Context is the context to callback with
Wnode has event to deliver
Return Value:
--*/
{
try
{
(*Callback)(Wnode, Context);
} except(EXCEPTION_EXECUTE_HANDLER) {
WmipDebugPrint(("NotificationCallbackRoutine threw exception %d\n",
GetExceptionCode()));
}
}
#ifdef MEMPHIS
ULONG WmipExternalNotificationThread(
PNOTIFDELIVERYCTX NDContext
)
/*++
Routine Description:
This routine is the thread function used to deliver events to event
consumers on memphis.
Arguments:
NDContext specifies the information about how to callback the application
with the event.
Return Value:
--*/
{
WmipExternalNotification(NDContext->Callback,
NDContext->Context,
NDContext->Wnode);
WmipFree(NDContext);
return(0);
}
#endif
void
WmipProcessExternalEvent(
PWNODE_HEADER Wnode,
ULONG WnodeSize,
PVOID DeliveryInfo,
ULONG_PTR DeliveryContext,
ULONG NotificationFlags
)
{
HANDLE ThreadHandle;
PNOTIFDELIVERYCTX NDContext;
PWNODE_HEADER *WnodePtr;
BOOLEAN WnodePtrOk;
PWNODE_HEADER WnodeCopy;
DWORD ThreadId;
BOOLEAN PostOk;
ULONG Status;
PVOID NotificationAddress;
PVOID NotificationContext;
NotificationAddress = DeliveryInfo;
NotificationContext = (PVOID)DeliveryContext;
if (NotificationFlags & NOTIFICATION_FLAG_CALLBACK_DIRECT)
{
//
// Callback notifications can happen in this thread or a new
// thread. It is up to the server to decide.
#ifdef MEMPHIS
if (NotificationFlags & DCREF_FLAG_NO_EXTRA_THREAD)
{
WmipExternalNotification(
(NOTIFICATIONCALLBACK)NotificationAddress,
(ULONG_PTR)NotificationContext,
Wnode);
} else {
NDContext = WmipAlloc(FIELD_OFFSET(NOTIFDELIVERYCTX,
WnodeBuffer) + WnodeSize);
if (NDContext != NULL)
{
NDContext->Callback = (NOTIFICATIONCALLBACK)NotificationAddress;
NDContext->Context = (ULONG_PTR)NotificationContext;
WnodeCopy = (PWNODE_HEADER)NDContext->WnodeBuffer;
memcpy(WnodeCopy, Wnode, WnodeSize);
NDContext->Wnode = WnodeCopy;
ThreadHandle = CreateThread(NULL,
0,
WmipExternalNotificationThread,
NDContext,
0,
&ThreadId);
if (ThreadHandle != NULL)
{
CloseHandle(ThreadHandle);
} else {
WmipDebugPrint(("WMI: Event dropped due to thread creation failure\n"));
}
} else {
WmipDebugPrint(("WMI: Event dropped due to lack of memory\n"));
}
}
#else
//
// On NT we deliver events in this thread since
// the service is using async rpc.
WmipExternalNotification(
(NOTIFICATIONCALLBACK)NotificationAddress,
(ULONG_PTR)NotificationContext,
Wnode);
#endif
}
}
void WmipInternalNotification(
IN PWNODE_HEADER Wnode,
IN NOTIFICATIONCALLBACK Callback,
IN ULONG_PTR DeliveryContext,
IN BOOLEAN IsAnsi
)
{
ULONG ActionCode, Cookie;
PUCHAR Buffer = (PUCHAR)Wnode;
//
// This is an internal event, which is really a callback
// from kernel mode
//
ActionCode = Wnode->ProviderId;
Cookie = Wnode->CountLost;
#if DBG
WmipDebugPrint(("WMI: Got internal event Action %d, Cookie %x\n",
ActionCode, Cookie));
#endif
// if this is a trace guid enable/disable call use the cookie
// to get the address
if ( (Wnode->Flags & WNODE_FLAG_TRACED_GUID) || (ActionCode == WmiMBRequest) )
{
switch (ActionCode) {
case WmiEnableEvents:
case WmiDisableEvents:
{
WMIDPREQUEST WmiDPRequest;
PVOID RequestAddress;
PVOID RequestContext;
ULONG Status;
ULONG BufferSize = Wnode->BufferSize;
if (Wnode->BufferSize >= (sizeof(WNODE_HEADER) + sizeof(ULONG64)) ) {
PULONG64 pLoggerContext = (PULONG64)(Buffer + sizeof(WNODE_HEADER));
Wnode->HistoricalContext = *pLoggerContext;
}
else {
#if DBG
WmipDebugPrint(("WMI: Wnode size %d is small for Trace notifications\n", Wnode->BufferSize));
#endif
}
if (WmipLookupCookie(Cookie,
&Wnode->Guid,
&RequestAddress,
&RequestContext)) {
WmiDPRequest = (WMIDPREQUEST)RequestAddress;
try
{
#ifndef MEMPHIS
WmipGenericTraceEnable(Wnode->ProviderId, Buffer, (PVOID*)&WmiDPRequest);
#endif
if (*WmiDPRequest != NULL) {
Status = (*WmiDPRequest)(Wnode->ProviderId,
RequestContext,
&BufferSize,
Buffer);
}
else
Status = ERROR_WMI_DP_NOT_FOUND;
} except (EXCEPTION_EXECUTE_HANDLER) {
#if DBG
Status = GetExceptionCode();
WmipDebugPrint(("WMI: Service request call caused an exception %d\n",
Status));
#endif
Status = ERROR_WMI_DP_FAILED;
}
}
break;
}
case WmiMBRequest:
{
PGUIDNOTIFICATION GNEntry;
PVOID DeliveryInfo = NULL;
ULONG_PTR DeliveryContext1;
ULONG i;
PWMI_LOGGER_INFORMATION LoggerInfo;
if (Wnode->BufferSize < (sizeof(WNODE_HEADER) + sizeof(WMI_LOGGER_INFORMATION)) )
{
#if DBG
SetLastError(ERROR_WMI_DP_FAILED);
WmipDebugPrint(("WMI: WmiMBRequest with invalid buffer size %d\n",
Wnode->BufferSize));
#endif
return;
}
LoggerInfo = (PWMI_LOGGER_INFORMATION) ((PUCHAR)Wnode + sizeof(WNODE_HEADER));
GNEntry = WmipFindGuidNotification(&LoggerInfo->Wnode.Guid);
if (GNEntry != NULL)
{
WmipEnterPMCritSection();
for (i = 0; i < GNEntry->NotifyeeCount; i++)
{
if ((GNEntry->Notifyee[i].DeliveryInfo != NULL) &&
(! WmipIsNotifyeePendingClose(&(GNEntry->Notifyee[i]))))
{
//
// TODO: We expect only one entry here. Restrict at registration.
//
DeliveryInfo = GNEntry->Notifyee[i].DeliveryInfo;
DeliveryContext1 = GNEntry->Notifyee[i].DeliveryContext;
break;
}
}
WmipLeavePMCritSection();
WmipDereferenceGNEntry(GNEntry);
}
if (DeliveryInfo != NULL)
{
LoggerInfo->Wnode.CountLost = Wnode->CountLost;
WmipProcessUMRequest(LoggerInfo, DeliveryInfo, Wnode->Version);
}
break;
}
default:
{
#if DBG
SetLastError(ERROR_WMI_DP_FAILED);
WmipDebugPrint(("WMI: WmiMBRequest failed. Delivery Info not found\n" ));
#endif
}
}
} else if (IsEqualGUID(&Wnode->Guid, &GUID_MOF_RESOURCE_ADDED_NOTIFICATION) ||
IsEqualGUID(&Wnode->Guid, &GUID_MOF_RESOURCE_REMOVED_NOTIFICATION) )
{
switch (ActionCode)
{
case MOFEVENT_ACTION_IMAGE_PATH:
case MOFEVENT_ACTION_REGISTRY_PATH:
{
//
// We got a MOF resource added or removed notification. We have
// to convert from regpath to imagepath and then get the list
// of MUI image paths
//
WmipProcessMofAddRemoveEvent((PWNODE_SINGLE_INSTANCE)Wnode,
Callback,
DeliveryContext,
IsAnsi);
break;
}
case MOFEVENT_ACTION_LANGUAGE_CHANGE:
{
//
// This is a notification for adding or removing a language
// from the system. We need to figure out which language is
// coming or going and then build a list of the affected mof
// resources and send mof added or removed notifications for
// all mof resources
//
WmipProcessLanguageAddRemoveEvent((PWNODE_SINGLE_INSTANCE)Wnode,
Callback,
DeliveryContext,
IsAnsi);
break;
}
default:
{
WmipAssert(FALSE);
}
}
}
}
void WmipConvertEventToAnsi(
PWNODE_HEADER Wnode
)
{
PWCHAR WPtr;
if (Wnode->Flags & WNODE_FLAG_ALL_DATA)
{
WmipConvertWADToAnsi((PWNODE_ALL_DATA)Wnode);
} else if ((Wnode->Flags & WNODE_FLAG_SINGLE_INSTANCE) ||
(Wnode->Flags & WNODE_FLAG_SINGLE_ITEM)) {
WPtr = (PWCHAR)OffsetToPtr(Wnode,
((PWNODE_SINGLE_INSTANCE)Wnode)->OffsetInstanceName);
WmipCountedUnicodeToCountedAnsi(WPtr, (PCHAR)WPtr);
}
Wnode->Flags |= WNODE_FLAG_ANSI_INSTANCENAMES;
}
void WmipDeliverAllEvents(
PUCHAR Buffer,
ULONG BufferSize
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
PWNODE_HEADER Wnode = (PWNODE_HEADER)Buffer;
ULONG Linkage = 1;
ULONG CompositeFlags;
ULONG i;
PGUIDNOTIFICATION GNEntry;
ULONG Flags;
PVOID DeliveryInfo;
ULONG_PTR DeliveryContext;
ULONG WnodeSize;
ULONG CurrentOffset;
#if DBG
PWNODE_HEADER LastWnode;
#endif
CurrentOffset = 0;
while (Linkage != 0)
{
//
// External notifications are handled here
Linkage = Wnode->Linkage;
Wnode->Linkage = 0;
if (Wnode->Flags & WNODE_FLAG_INTERNAL)
{
//
// This is an internal event, which is really a callback
// from kernel mode
//
WmipInternalNotification(Wnode,
NULL,
0,
FALSE);
} else {
//
// This is a plain old event, figure out who owns it and'
// go deliver it
//
GNEntry = WmipFindGuidNotification(&Wnode->Guid);
if (GNEntry != NULL)
{
CompositeFlags = 0;
WnodeSize = Wnode->BufferSize;
for (i = 0; i < GNEntry->NotifyeeCount; i++)
{
WmipEnterPMCritSection();
Flags = GNEntry->Notifyee[i].Flags;
DeliveryInfo = GNEntry->Notifyee[i].DeliveryInfo;
DeliveryContext = GNEntry->Notifyee[i].DeliveryContext;
WmipLeavePMCritSection();
if ((DeliveryInfo != NULL) &&
((Flags & NOTIFICATION_FLAG_PENDING_CLOSE) == 0) &&
((Flags & DCREF_FLAG_ANSI) == 0))
{
WmipProcessExternalEvent(Wnode,
WnodeSize,
DeliveryInfo,
DeliveryContext,
Flags);
}
CompositeFlags |= Flags;
}
//
// If there is any demand for ANSI events then convert
// event to ansi and send it off
if (CompositeFlags & DCREF_FLAG_ANSI)
{
//
// Caller wants ansi notification - convert
// instance names
//
WmipConvertEventToAnsi(Wnode);
for (i = 0; i < GNEntry->NotifyeeCount; i++)
{
WmipEnterPMCritSection();
Flags = GNEntry->Notifyee[i].Flags;
DeliveryInfo = GNEntry->Notifyee[i].DeliveryInfo;
DeliveryContext = GNEntry->Notifyee[i].DeliveryContext;
WmipLeavePMCritSection();
if ((DeliveryInfo != NULL) &&
((Flags & NOTIFICATION_FLAG_PENDING_CLOSE) == 0) &&
(Flags & DCREF_FLAG_ANSI))
{
WmipProcessExternalEvent(Wnode,
WnodeSize,
DeliveryInfo,
DeliveryContext,
Flags);
}
}
}
WmipDereferenceGNEntry(GNEntry);
}
}
#if DBG
LastWnode = Wnode;
#endif
Wnode = (PWNODE_HEADER)OffsetToPtr(Wnode, Linkage);
CurrentOffset += Linkage;
if (CurrentOffset >= BufferSize)
{
WmipDebugPrint(("WMI: Invalid linkage field 0x%x in WNODE %p. Buffer %p, Length 0x%x\n",
Linkage, LastWnode, Buffer, BufferSize));
Linkage = 0;
}
}
}
LIST_ENTRY WmipGNHead = {&WmipGNHead, &WmipGNHead};
PLIST_ENTRY WmipGNHeadPtr = &WmipGNHead;
BOOLEAN
WmipDereferenceGNEntry(
PGUIDNOTIFICATION GNEntry
)
{
ULONG RefCount;
BOOLEAN IsFreed;
#if DBG
ULONG i;
#endif
WmipEnterPMCritSection();
RefCount = InterlockedDecrement(&GNEntry->RefCount);
if (RefCount == 0)
{
RemoveEntryList(&GNEntry->GNList);
WmipLeavePMCritSection();
#if DBG
for (i = 0; i < GNEntry->NotifyeeCount; i++)
{
WmipAssert(GNEntry->Notifyee[i].DeliveryInfo == NULL);
}
#endif
if (GNEntry->NotifyeeCount != STATIC_NOTIFYEE_COUNT)
{
WmipFree(GNEntry->Notifyee);
}
WmipFreeGNEntry(GNEntry);
IsFreed = TRUE;
} else {
IsFreed = FALSE;
WmipLeavePMCritSection();
}
return(IsFreed);
}
PGUIDNOTIFICATION
WmipFindGuidNotification(
LPGUID Guid
)
{
PLIST_ENTRY GNList;
PGUIDNOTIFICATION GNEntry;
WmipEnterPMCritSection();
GNList = WmipGNHead.Flink;
while (GNList != &WmipGNHead)
{
GNEntry = (PGUIDNOTIFICATION)CONTAINING_RECORD(GNList,
GUIDNOTIFICATION,
GNList);
if (IsEqualGUID(Guid, &GNEntry->Guid))
{
WmipAssert(GNEntry->RefCount > 0);
WmipReferenceGNEntry(GNEntry);
WmipLeavePMCritSection();
return(GNEntry);
}
GNList = GNList->Flink;
}
WmipLeavePMCritSection();
return(NULL);
}
ULONG
WmipAddToGNList(
LPGUID Guid,
PVOID DeliveryInfo,
ULONG_PTR DeliveryContext,
ULONG Flags,
HANDLE GuidHandle
)
{
PGUIDNOTIFICATION GNEntry;
ULONG NewCount;
PNOTIFYEE NewNotifyee;
BOOLEAN AllFull;
ULONG EmptySlot;
ULONG i;
#if DBG
CHAR s[MAX_PATH];
#endif
WmipEnterPMCritSection();
GNEntry = WmipFindGuidNotification(Guid);
if (GNEntry == NULL)
{
GNEntry = WmipAllocGNEntry();
if (GNEntry == NULL)
{
WmipLeavePMCritSection();
return(ERROR_NOT_ENOUGH_MEMORY);
}
memset(GNEntry, 0, sizeof(GUIDNOTIFICATION));
GNEntry->Guid = *Guid;
GNEntry->RefCount = 1;
GNEntry->NotifyeeCount = STATIC_NOTIFYEE_COUNT;
GNEntry->Notifyee = GNEntry->StaticNotifyee;
InsertHeadList(&WmipGNHead, &GNEntry->GNList);
}
//
// We have got a GUIDNOTIFICATION by newly allocating one or by finding
// an existing one.
AllFull = TRUE;
for (i = 0; i < GNEntry->NotifyeeCount; i++)
{
if ((GNEntry->Notifyee[i].DeliveryInfo == DeliveryInfo) &&
(! WmipIsNotifyeePendingClose(&GNEntry->Notifyee[i])))
{
WmipDebugPrint(("WMI: Duplicate Notification Enable for guid %s, 0x%x\n",
GuidToStringA(s, Guid), DeliveryInfo));
WmipLeavePMCritSection();
WmipDereferenceGNEntry(GNEntry);
return(ERROR_WMI_ALREADY_ENABLED);
} else if (AllFull && (GNEntry->Notifyee[i].DeliveryInfo == NULL)) {
EmptySlot = i;
AllFull = FALSE;
}
}
if (! AllFull)
{
GNEntry->Notifyee[EmptySlot].DeliveryInfo = DeliveryInfo;
GNEntry->Notifyee[EmptySlot].DeliveryContext = DeliveryContext;
GNEntry->Notifyee[EmptySlot].Flags = Flags;
GNEntry->Notifyee[EmptySlot].GuidHandle = GuidHandle;
WmipDebugPrint(("WMI: [%x - %x] Handle %x for %s added to GN at %p\n",
GetCurrentProcessId(), GetCurrentThreadId(),
GuidHandle,
GuidToStringA(s, Guid),
&GNEntry->Notifyee[EmptySlot]));
WmipLeavePMCritSection();
return(ERROR_SUCCESS);
}
//
// All Notifyee structs are full so allocate a new chunk
NewCount = GNEntry->NotifyeeCount * 2;
NewNotifyee = WmipAlloc(NewCount * sizeof(NOTIFYEE));
if (NewNotifyee == NULL)
{
WmipLeavePMCritSection();
WmipDereferenceGNEntry(GNEntry);
return(ERROR_NOT_ENOUGH_MEMORY);
}
memset(NewNotifyee, 0, NewCount * sizeof(NOTIFYEE));
memcpy(NewNotifyee, GNEntry->Notifyee,
GNEntry->NotifyeeCount * sizeof(NOTIFYEE));
if (GNEntry->NotifyeeCount != STATIC_NOTIFYEE_COUNT)
{
WmipFree(GNEntry->Notifyee);
}
GNEntry->Notifyee = NewNotifyee;
GNEntry->NotifyeeCount = NewCount;
GNEntry->Notifyee[i].DeliveryInfo = DeliveryInfo;
GNEntry->Notifyee[i].DeliveryContext = DeliveryContext;
GNEntry->Notifyee[i].Flags = Flags;
GNEntry->Notifyee[i].GuidHandle = GuidHandle;
WmipDebugPrint(("WMI: [%x - %x] Handle %x for %s added to GN at %p\n",
GetCurrentProcessId(), GetCurrentThreadId(),
GuidHandle,
GuidToStringA(s, Guid),
&GNEntry->Notifyee[i]));
WmipLeavePMCritSection();
return(ERROR_SUCCESS);
}
BOOLEAN WmipCloseNotifyee(
PNOTIFYEE Notifyee,
PGUIDNOTIFICATION GuidNotification
)
{
//
// This routine assumes the PM Criticial Section is held
//
WmipDebugPrint(("WMI: [%x - %x] Handle %x Closed at %p\n",
GetCurrentProcessId(), GetCurrentThreadId(),
Notifyee->GuidHandle,
Notifyee));
CloseHandle(Notifyee->GuidHandle);
Notifyee->DeliveryInfo = NULL;
Notifyee->Flags = 0;
return(WmipDereferenceGNEntry(GuidNotification));
}
void WmipMarkPendingCloseNotifyee(
PNOTIFYEE Notifyee
#if DBG
, LPGUID Guid
#endif
)
{
WMIMARKASCLOSED MarkAsClosed;
ULONG ReturnSize;
NTSTATUS Status;
#if DBG
char s[MAX_PATH];
#endif
//
// This routine assumes the PM Critical Section is held
//
//
// The pump thread is running we need to
// sync with it. Mark the handle as pending
// closure. Call into the kernel and inform it
// that the handle should no longer receive
// events. The pump thread will do the dirty
// work of closing the handle. Also
// the pump thread will unreference the GNEntry so that
// it doesn't go away until after the handle is closed.
// Lastly the pump thread needs to reset the
// DeliveryInfo memory to NULL so that the slot is not
// reused.
//
WmipDebugPrint(("WMI: [%x - %x] Handle %x for %s marked for close GN at %p\n",
GetCurrentProcessId(), GetCurrentThreadId(),
Notifyee->GuidHandle,
GuidToStringA(s, Guid),
Notifyee));
Notifyee->Flags |= NOTIFICATION_FLAG_PENDING_CLOSE;
WmipSetHandle3264(MarkAsClosed.Handle, Notifyee->GuidHandle);
Status = WmipSendWmiKMRequest(NULL,
IOCTL_WMI_MARK_HANDLE_AS_CLOSED,
&MarkAsClosed,
sizeof(MarkAsClosed),
NULL,
0,
&ReturnSize,
NULL);
//
// Only enable this for testing. If the request fails then it is not a
// fatal situaion
//
// WmipAssert(Status == ERROR_SUCCESS);
}
ULONG
WmipRemoveFromGNList(
LPGUID Guid,
PVOID DeliveryInfo
)
{
PGUIDNOTIFICATION GNEntry;
ULONG i;
ULONG Count;
ULONG Status;
GNEntry = WmipFindGuidNotification(Guid);
if (GNEntry != NULL)
{
Status = ERROR_INVALID_PARAMETER;
Count = 0;
WmipEnterPMCritSection();
for (i = 0; i < GNEntry->NotifyeeCount; i++)
{
if (GNEntry->Notifyee[i].DeliveryInfo != NULL)
{
if ((GNEntry->Notifyee[i].DeliveryInfo == DeliveryInfo) &&
( ! WmipIsNotifyeePendingClose(&GNEntry->Notifyee[i])) &&
(Status != ERROR_SUCCESS))
{
if ((WmipPumpState == EVENT_PUMP_ZERO) ||
(WmipPumpState == EVENT_PUMP_IDLE) )
{
//
// If the pump thread is not running then we
// don't need to worry about synchronizing with
// it. We can go ahead and close the handle and
// clean up the GNLIST
//
WmipCloseNotifyee(&GNEntry->Notifyee[i],
GNEntry);
} else {
//
// Since the pump thread is running we need to
// postpone the actual handle closure to the
// pump thread.
//
WmipMarkPendingCloseNotifyee(&GNEntry->Notifyee[i]
#if DBG
, Guid
#endif
);
}
Status = ERROR_SUCCESS;
break;
} else if (! WmipIsNotifyeePendingClose(&GNEntry->Notifyee[i])) {
Count++;
}
}
}
//
// This hack will allow removal from the GNLIST in the case that the
// passed DeliveryInfo does not match the DeliveryInfo in the GNEntry.
// This is allowed only when there is only one NOTIFYEE in the GNENTRY
// In the past we only supported one notifyee per guid in a process
// and so we allowed the caller not to pass a valid DeliveryInfo when
// unrefistering.
if ((Status != ERROR_SUCCESS) &&
(GNEntry->NotifyeeCount == STATIC_NOTIFYEE_COUNT) &&
(Count == 1))
{
if ((GNEntry->Notifyee[0].DeliveryInfo != NULL) &&
( ! WmipIsNotifyeePendingClose(&GNEntry->Notifyee[0])))
{
if ((WmipPumpState == EVENT_PUMP_ZERO) ||
(WmipPumpState == EVENT_PUMP_IDLE) )
{
WmipCloseNotifyee(&GNEntry->Notifyee[0],
GNEntry);
} else {
//
// Since the pump thread is running we need to
// postpone the actual handle closure to the
// pump thread.
//
WmipMarkPendingCloseNotifyee(&GNEntry->Notifyee[0]
#if DBG
, Guid
#endif
);
}
Status = ERROR_SUCCESS;
} else if ((GNEntry->Notifyee[1].DeliveryInfo != NULL) &&
( ! WmipIsNotifyeePendingClose(&GNEntry->Notifyee[1]))) {
if ((WmipPumpState == EVENT_PUMP_ZERO) ||
(WmipPumpState == EVENT_PUMP_IDLE) )
{
WmipCloseNotifyee(&GNEntry->Notifyee[1],
GNEntry);
} else {
//
// Since the pump thread is running we need to
// postpone the actual handle closure to the
// pump thread.
//
WmipMarkPendingCloseNotifyee(&GNEntry->Notifyee[1]
#if DBG
, Guid
#endif
);
}
Status = ERROR_SUCCESS;
}
}
WmipLeavePMCritSection();
WmipDereferenceGNEntry(GNEntry);
} else {
Status = ERROR_WMI_ALREADY_DISABLED;
}
return(Status);
}
PVOID WmipAllocDontFail(
ULONG SizeNeeded,
BOOLEAN *HoldCritSect
)
{
PVOID Buffer;
do
{
Buffer = WmipAlloc(SizeNeeded);
if (Buffer != NULL)
{
return(Buffer);
}
//
// Out of memory so we'll sleep and hope that things will get
// better later
//
if (*HoldCritSect)
{
//
// If we are holding the PM critical section then we need
// to release it. The caller is going to need to check if
// the critical section was released and if so then deal
// with it
//
*HoldCritSect = FALSE;
WmipLeavePMCritSection();
}
Sleep(250);
} while (1);
}
void WmipProcessEventBuffer(
PUCHAR Buffer,
ULONG ReturnSize,
PUCHAR *PrimaryBuffer,
ULONG *PrimaryBufferSize,
PUCHAR *BackupBuffer,
ULONG *BackupBufferSize,
BOOLEAN ReallocateBuffers
)
{
PWNODE_TOO_SMALL WnodeTooSmall;
ULONG SizeNeeded;
BOOLEAN HoldCritSection;
WnodeTooSmall = (PWNODE_TOO_SMALL)Buffer;
if ((ReturnSize == sizeof(WNODE_TOO_SMALL)) &&
(WnodeTooSmall->WnodeHeader.Flags & WNODE_FLAG_TOO_SMALL))
{
//
// The buffer passed to kernel mode was too small
// so we need to make it larger and then try the
// request again.
//
if (ReallocateBuffers)
{
//
// Only do this if the caller is prepared for us to
// allocate a new set of buffers
//
SizeNeeded = WnodeTooSmall->SizeNeeded;
WmipAssert(*PrimaryBuffer != NULL);
WmipFree(*PrimaryBuffer);
WmipDebugPrint(("WMI: [%x - %x] Free primary %x\n",
GetCurrentProcessId(), GetCurrentThreadId(),
*PrimaryBuffer));
HoldCritSection = FALSE;
*PrimaryBuffer = WmipAllocDontFail(SizeNeeded, &HoldCritSection);
WmipDebugPrint(("WMI: [%x - %x] Realloc primary %x\n",
GetCurrentProcessId(), GetCurrentThreadId(),
*PrimaryBuffer));
*PrimaryBufferSize = SizeNeeded;
WmipAssert(*BackupBuffer != NULL);
WmipFree(*BackupBuffer);
WmipDebugPrint(("WMI: [%x - %x] Free backup %x\n",
GetCurrentProcessId(), GetCurrentThreadId(),
*BackupBuffer));
HoldCritSection = FALSE;
*BackupBuffer = WmipAllocDontFail(SizeNeeded, &HoldCritSection);
WmipDebugPrint(("WMI: [%x - %x] Realloc backup %x\n",
GetCurrentProcessId(), GetCurrentThreadId(),
*BackupBuffer));
*BackupBufferSize = SizeNeeded;
}
} else if (ReturnSize >= sizeof(WNODE_HEADER)) {
//
// The buffer return from kernel looks good so go and
// deliver the events returned
//
WmipDeliverAllEvents(Buffer, ReturnSize);
} else {
//
// If this completes successfully then we expect a decent size, but
// we didn't get one
//
WmipDebugPrint(("WMI: Bad size 0x%x returned for notification query %p\n",
ReturnSize, Buffer));
WmipAssert(FALSE);
}
}
ULONG
WmipReceiveNotifications(
IN ULONG HandleCount,
IN HANDLE *HandleList,
IN NOTIFICATIONCALLBACK Callback,
IN ULONG_PTR DeliveryContext,
IN BOOLEAN IsAnsi,
IN ULONG Action,
IN PUSER_THREAD_START_ROUTINE UserModeCallback,
IN HANDLE ProcessHandle
)
{
ULONG Status;
ULONG ReturnSize;
PWMIRECEIVENOTIFICATION RcvNotification;
ULONG RcvNotificationSize;
PUCHAR Buffer;
ULONG BufferSize;
PWNODE_TOO_SMALL WnodeTooSmall;
PWNODE_HEADER Wnode;
ULONG i;
ULONG Linkage;
if (HandleCount == 0)
{
SetLastError(ERROR_INVALID_PARAMETER);
return(ERROR_INVALID_PARAMETER);
}
RcvNotificationSize = sizeof(WMIRECEIVENOTIFICATION) +
((HandleCount-1) * sizeof(HANDLE3264));
RcvNotification = WmipAlloc(RcvNotificationSize);
if (RcvNotification != NULL)
{
Status = ERROR_SUCCESS;
RcvNotification->Action = Action;
WmipSetPVoid3264(RcvNotification->UserModeCallback, UserModeCallback);
WmipSetHandle3264(RcvNotification->UserModeProcess, ProcessHandle);
RcvNotification->HandleCount = HandleCount;
for (i = 0; i < HandleCount; i++)
{
try
{
RcvNotification->Handles[i].Handle = HandleList[i];
} except(EXCEPTION_EXECUTE_HANDLER) {
Status = ERROR_INVALID_PARAMETER;
break;
}
}
BufferSize = 0x1000;
Status = ERROR_INSUFFICIENT_BUFFER;
while (Status == ERROR_INSUFFICIENT_BUFFER)
{
Buffer = WmipAlloc(BufferSize);
if (Buffer != NULL)
{
Status = WmipSendWmiKMRequest(NULL,
IOCTL_WMI_RECEIVE_NOTIFICATIONS,
RcvNotification,
RcvNotificationSize,
Buffer,
BufferSize,
&ReturnSize,
NULL);
if (Status == ERROR_SUCCESS)
{
WnodeTooSmall = (PWNODE_TOO_SMALL)Buffer;
if ((ReturnSize == sizeof(WNODE_TOO_SMALL)) &&
(WnodeTooSmall->WnodeHeader.Flags & WNODE_FLAG_TOO_SMALL))
{
//
// The buffer passed to kernel mode was too small
// so we need to make it larger and then try the
// request again
//
BufferSize = WnodeTooSmall->SizeNeeded;
Status = ERROR_INSUFFICIENT_BUFFER;
} else {
//
// We got a buffer of notifications so lets go
// process them and callback the caller
//
Wnode = (PWNODE_HEADER)Buffer;
do
{
Linkage = Wnode->Linkage;
Wnode->Linkage = 0;
if (Wnode->Flags & WNODE_FLAG_INTERNAL)
{
//
// Go and process the internal
// notification
//
WmipInternalNotification(Wnode,
Callback,
DeliveryContext,
IsAnsi);
} else {
if (IsAnsi)
{
//
// Caller wants ansi notification - convert
// instance names
//
WmipConvertEventToAnsi(Wnode);
}
//
// Now go and deliver this event
//
WmipExternalNotification(Callback,
DeliveryContext,
Wnode);
}
Wnode = (PWNODE_HEADER)OffsetToPtr(Wnode, Linkage);
} while (Linkage != 0);
}
}
WmipFree(Buffer);
} else {
Status = ERROR_NOT_ENOUGH_MEMORY;
}
}
WmipFree(RcvNotification);
} else {
Status = ERROR_NOT_ENOUGH_MEMORY;
}
SetLastError(Status);
return(Status);
}
void WmipMakeEventCallbacks(
IN PWNODE_HEADER Wnode,
IN NOTIFICATIONCALLBACK Callback,
IN ULONG_PTR DeliveryContext,
IN BOOLEAN IsAnsi
)
{
WmipAssert((Wnode->Flags & WNODE_FLAG_INTERNAL) == 0);
if (Callback == NULL)
{
//
// This event needs to be sent to all consumers
//
WmipDeliverAllEvents((PUCHAR)Wnode,
Wnode->BufferSize);
} else {
//
// This event is targetted at a specific consumer
//
if (IsAnsi)
{
//
// Caller wants ansi notification - convert
// instance names
//
WmipConvertEventToAnsi(Wnode);
}
//
// Now go and deliver this event
//
WmipExternalNotification(Callback,
DeliveryContext,
Wnode);
}
}
void WmipClosePendingHandles(
)
{
PLIST_ENTRY GuidNotificationList, GuidNotificationListNext;
PGUIDNOTIFICATION GuidNotification;
ULONG i;
PNOTIFYEE Notifyee;
WmipEnterPMCritSection();
GuidNotificationList = WmipGNHead.Flink;
while (GuidNotificationList != &WmipGNHead)
{
GuidNotification = CONTAINING_RECORD(GuidNotificationList,
GUIDNOTIFICATION,
GNList);
GuidNotificationListNext = GuidNotificationList->Flink;
for (i = 0; i < GuidNotification->NotifyeeCount; i++)
{
Notifyee = &GuidNotification->Notifyee[i];
if ((Notifyee->DeliveryInfo != NULL) &&
(WmipIsNotifyeePendingClose(Notifyee)))
{
//
// This notifyee is pending closure so we clean it up
// now. We need to close the handle, reset the
// DeliveryInfo field and unreference the
// GuidNotification. Note that unreferencing may cause
// the GuidNotification to go away
//
WmipDebugPrint(("WMI: [%x - %x] Pump closing handle 0x%x\n",
GetCurrentProcessId(), GetCurrentThreadId(),
Notifyee->GuidHandle));
if (WmipCloseNotifyee(Notifyee,
GuidNotification))
{
//
// GuidNotification has been removed from the list.
// We jump out of all processing of this
// GuidNotification and move onto the next one
//
break;
}
}
}
GuidNotificationList = GuidNotificationListNext;
}
WmipLeavePMCritSection();
}
void WmipBuildReceiveNotification(
PUCHAR *BufferPtr,
ULONG *BufferSizePtr,
ULONG *RequestSize,
ULONG Action,
HANDLE ProcessHandle
)
{
ULONG GuidCount;
PUCHAR Buffer;
ULONG BufferSize;
PLIST_ENTRY GuidNotificationList;
PGUIDNOTIFICATION GuidNotification;
PWMIRECEIVENOTIFICATION ReceiveNotification;
ULONG SizeNeeded;
ULONG i;
PNOTIFYEE Notifyee;
ULONG ReturnSize;
ULONG Status;
BOOLEAN HoldCritSection;
BOOLEAN HaveGroupHandle;
Buffer = *BufferPtr;
BufferSize = *BufferSizePtr;
ReceiveNotification = (PWMIRECEIVENOTIFICATION)Buffer;
TryAgain:
GuidCount = 0;
SizeNeeded = FIELD_OFFSET(WMIRECEIVENOTIFICATION, Handles);
//
// Loop over all guid notifications and build an ioctl request for
// all of them
//
WmipEnterPMCritSection();
GuidNotificationList = WmipGNHead.Flink;
while (GuidNotificationList != &WmipGNHead)
{
GuidNotification = CONTAINING_RECORD(GuidNotificationList,
GUIDNOTIFICATION,
GNList);
HaveGroupHandle = FALSE;
for (i = 0; i < GuidNotification->NotifyeeCount; i++)
{
Notifyee = &GuidNotification->Notifyee[i];
if ((Notifyee->DeliveryInfo != NULL) &&
( ! WmipIsNotifyeePendingClose(Notifyee)))
{
if (((! HaveGroupHandle) ||
((Notifyee->Flags & NOTIFICATION_FLAG_GROUPED_EVENT) == 0)))
{
//
// If there is an active handle in the notifyee slot
// and we either have not already inserted the group
// handle for this guid or the slot is not part of the
// guid group, then we insert the handle into the list
//
SizeNeeded += sizeof(HANDLE3264);
if (SizeNeeded > BufferSize)
{
//
// We need to grow the size of the buffer. Alloc a
// bigger buffer, copy over
//
BufferSize *= 2;
HoldCritSection = TRUE;
Buffer = WmipAllocDontFail(BufferSize, &HoldCritSection);
memcpy(Buffer, ReceiveNotification, *BufferSizePtr);
WmipFree(*BufferPtr);
*BufferPtr = Buffer;
*BufferSizePtr = BufferSize;
ReceiveNotification = (PWMIRECEIVENOTIFICATION)Buffer;
if (! HoldCritSection)
{
//
// Critical section was released within
// WmipAllocDontFail since we had to block. So
// everything could have changed. We need to go
// back and start over again
//
goto TryAgain;
}
}
WmipSetHandle3264(ReceiveNotification->Handles[GuidCount],
Notifyee->GuidHandle);
GuidCount++;
WmipDebugPrint(("WMI: [%x - %x] Add Handle to request %x\n",
GetCurrentProcessId(), GetCurrentThreadId(),
Notifyee->GuidHandle));
if (Notifyee->Flags & NOTIFICATION_FLAG_GROUPED_EVENT)
{
//
// This was a guid group handle and we did insert
// it into the list so we don't want to insert it
// again
//
HaveGroupHandle = TRUE;
}
}
}
}
GuidNotificationList = GuidNotificationList->Flink;
}
WmipLeavePMCritSection();
ReceiveNotification->HandleCount = GuidCount;
ReceiveNotification->Action = Action;
WmipSetPVoid3264(ReceiveNotification->UserModeCallback, WmipEventPumpFromKernel);
WmipSetHandle3264(ReceiveNotification->UserModeProcess, ProcessHandle);
*RequestSize = SizeNeeded;
}
#if _MSC_FULL_VER >= 13008827
#pragma warning(push)
#pragma warning(disable:4715) // Not all control paths return (due to infinite loop)
#endif
ULONG WmipEventPump(
PVOID Param
)
{
LPOVERLAPPED ActiveOverlapped, DeadOverlapped;
LPOVERLAPPED PrimaryOverlapped;
LPOVERLAPPED BackupOverlapped;
PUCHAR ActiveBuffer, DeadBuffer;
ULONG ActiveBufferSize, DeadBufferSize;
PUCHAR PrimaryBuffer, BackupBuffer;
ULONG PrimaryBufferSize, BackupBufferSize;
ULONG ReturnSize, DeadReturnSize;
ULONG Status, WaitStatus;
HANDLE HandleArray[2];
ULONG RequestSize;
WmipDebugPrint(("WMI: [%x - %x] New pump thread is started\n",
GetCurrentProcessId(), GetCurrentThreadId()));
//
// We need to hold off on letting the thread into the routine until
// the previous pump thread has had a chance to finish. This could
// occur if a GN is added/removed while the previous thread is
// finishing up or if an event is received as the previous thread
// is finishing up.
//
while (WmipIsPumpStopping())
{
//
// wait 50ms for the previous thread to finish up
//
WmipDebugPrint(("WMI: [%x - %x] waiting for pump to be stopped\n",
GetCurrentProcessId(), GetCurrentThreadId()));
Sleep(50);
}
//
// Next thing to do is to make sure that another pump thread isn't
// already running. This can happen in the case that both a GN is
// added or removed and an event reaches kernel and the kernel
// creates a new thread too. Right here we only let one of them
// win.
//
WmipEnterPMCritSection();
if ((WmipPumpState != EVENT_PUMP_IDLE) &&
(WmipPumpState != EVENT_PUMP_ZERO))
{
WmipDebugPrint(("WMI: [%x - %x] Exit pump since state is %d\n",
GetCurrentProcessId(), GetCurrentThreadId(),
WmipPumpState));
WmipLeavePMCritSection();
return(0);
} else {
WmipDebugPrint(("WMI: [%x - %x] pump thread now running\n",
GetCurrentProcessId(), GetCurrentThreadId()));
WmipPumpState = EVENT_PUMP_RUNNING;
WmipNewPumpThreadPending = FALSE;
WmipLeavePMCritSection();
}
//
// Make sure we have all resources we'll need to pump out events
// since there is no way that we can return an error to the original
// caller since we are on a new thread
//
WmipAssert(WmipEventDeviceHandle != NULL);
WmipAssert(WmipPumpCommandEvent != NULL);
WmipAssert(WmipMyProcessHandle != NULL);
WmipAssert(WmipEventBuffer1 != NULL);
WmipAssert(WmipEventBuffer2 != NULL);
WmipAssert(WmipOverlapped1.hEvent != NULL);
WmipAssert(WmipOverlapped2.hEvent != NULL);
ActiveOverlapped = NULL;
PrimaryOverlapped = &WmipOverlapped1;
PrimaryBuffer = WmipEventBuffer1;
PrimaryBufferSize = WmipEventBufferSize1;
BackupOverlapped = &WmipOverlapped2;
BackupBuffer = WmipEventBuffer2;
BackupBufferSize = WmipEventBufferSize2;
WmipDebugPrint(("WMI: [%x - %x] 1 buffer is %x, 2 buffer id %x\n",
GetCurrentProcessId(), GetCurrentThreadId(),
WmipEventBuffer1, WmipEventBuffer2));
HandleArray[0] = WmipPumpCommandEvent;
while(TRUE)
{
//
// Build request to receive events for all guids that are
// registered
//
WmipEnterPMCritSection();
if (IsListEmpty(&WmipGNHead))
{
//
// There are no events to be received so we cancel any
// outstanding requests and quietly exit this thread. Note
// that once we leave the critsec there could be another
// pump thread running so all we can do after that is exit.
//
CancelIo(WmipEventDeviceHandle);
//
// Enter the idle state which implies that all of the
// pump resources stay allocated when the thread is not
// running
//
WmipEventBuffer1 = PrimaryBuffer;
WmipEventBufferSize1 = PrimaryBufferSize;
WmipEventBuffer2 = BackupBuffer;
WmipEventBufferSize2 = BackupBufferSize;
WmipPumpState = EVENT_PUMP_IDLE;
WmipDebugPrint(("WMI: [%x - %x] No more GN, pump exiting, pump state EVENT_PUMP_IDLE\n",
GetCurrentProcessId(), GetCurrentThreadId()));
WmipLeavePMCritSection();
return(0);
}
WmipLeavePMCritSection();
if (ActiveOverlapped != NULL)
{
//
// If there was a previously outstanding request then
// we remember it and switch to the backup overlapped and
// and data buffer
//
DeadOverlapped = ActiveOverlapped;
DeadBuffer = ActiveBuffer;
DeadBufferSize = ActiveBufferSize;
//
// The request being mooted should be the current primary
//
WmipAssert(DeadOverlapped == PrimaryOverlapped);
WmipAssert(DeadBuffer == PrimaryBuffer);
//
// Use the backup request as the new primary
//
WmipAssert(BackupOverlapped != NULL);
WmipAssert(BackupBuffer != NULL);
PrimaryOverlapped = BackupOverlapped;
PrimaryBuffer = BackupBuffer;
PrimaryBufferSize = BackupBufferSize;
BackupOverlapped = NULL;
BackupBuffer = NULL;
} else {
//
// If there is no outstanding request then we don't worry about
// it
//
DeadOverlapped = NULL;
}
//
// Build and send the request down to kernel to receive events
//
RebuildRequest:
//
// Make sure any handles that are pending closure are closed
//
WmipClosePendingHandles();
WmipBuildReceiveNotification(&PrimaryBuffer,
&PrimaryBufferSize,
&RequestSize,
WmipIsPumpStopping() ? RECEIVE_ACTION_CREATE_THREAD :
RECEIVE_ACTION_NONE,
WmipMyProcessHandle);
ActiveOverlapped = PrimaryOverlapped;
ActiveBuffer = PrimaryBuffer;
ActiveBufferSize = PrimaryBufferSize;
Status = WmipSendWmiKMRequest(WmipEventDeviceHandle,
IOCTL_WMI_RECEIVE_NOTIFICATIONS,
ActiveBuffer,
RequestSize,
ActiveBuffer,
ActiveBufferSize,
&ReturnSize,
ActiveOverlapped);
if (DeadOverlapped != NULL)
{
if ((Status != ERROR_SUCCESS) &&
(Status != ERROR_IO_PENDING) &&
(Status != ERROR_OPERATION_ABORTED))
{
//
// There was a previous request which won't be cleared
// unless the new request returns pending, cancelled
// or success. So if the new request returns something
// else then we need to retry the request
//
WmipDebugPrint(("WMI: Event Poll error %d\n", Status));
Sleep(100);
goto RebuildRequest;
}
//
// The new request should have caused the old one to
// be completed
//
if (GetOverlappedResult(WmipEventDeviceHandle,
DeadOverlapped,
&DeadReturnSize,
TRUE))
{
//
// The dead request did succeed and was not failed by
// the receipt of the new request. This is a unlikely
// race condition where the requests crossed paths. So we
// need to process the events returned in the dead request.
// Now if the buffer returned was a WNODE_TOO_SMALL we want
// to ignore it at this point since we are not at a
// good position to reallocate the buffers - the
// primary buffer is already attached to the new
// request. That request is also going to return a
// WNODE_TOO_SMALL and in the processing of that one we will
// grow the buffers. So it is safe to ignore here.
// However we will still need to dispatch any real
// events received as they have been purged from KM.
//
if (DeadReturnSize != 0)
{
WmipDebugPrint(("WMI: [%x - %x] Process Dead overlapped %p events %p (0x%x)\n",
GetCurrentProcessId(), GetCurrentThreadId(),
DeadOverlapped, DeadBuffer, DeadReturnSize));
WmipProcessEventBuffer(DeadBuffer,
DeadReturnSize,
&PrimaryBuffer,
&PrimaryBufferSize,
&BackupBuffer,
&BackupBufferSize,
FALSE);
} else {
WmipAssert(WmipIsPumpStopping());
}
}
//
// Now make the completed request the backup request
//
WmipAssert(BackupOverlapped == NULL);
WmipAssert(BackupBuffer == NULL);
BackupOverlapped = DeadOverlapped;
BackupBuffer = DeadBuffer;
BackupBufferSize = DeadBufferSize;
}
if (Status == ERROR_IO_PENDING)
{
//
// if the ioctl pended then we wait until either an event
// is returned or a command needs processed
//
HandleArray[1] = ActiveOverlapped->hEvent;
WaitStatus = WaitForMultipleObjectsEx(2,
HandleArray,
FALSE,
WmipEventNotificationWait,
TRUE);
WmipDebugPrint(("WMI: [%x - %x] Done Waiting for RCV or command -> %d\n",
GetCurrentProcessId(), GetCurrentThreadId(),
WaitStatus));
} else {
//
// the ioctl completed immediately so we fake out the wait
//
WaitStatus = WAIT_OBJECT_0 + 1;
}
if (WaitStatus == WAIT_OBJECT_0 + 1)
{
if (Status == ERROR_IO_PENDING)
{
if (GetOverlappedResult(WmipEventDeviceHandle,
ActiveOverlapped,
&ReturnSize,
TRUE))
{
Status = ERROR_SUCCESS;
} else {
Status = GetLastError();
}
}
if (Status == ERROR_SUCCESS)
{
//
// We received some events from KM so we want to go and
// process them. If we got a WNODE_TOO_SMALL then the
// primary and backup buffers will get reallocated with
// the new size that is needed.
//
WmipDebugPrint(("WMI: [%x - %x] Process Active overlapped %p events %p (0x%x)\n",
GetCurrentProcessId(), GetCurrentThreadId(),
ActiveOverlapped, ActiveBuffer, ReturnSize));
if (ReturnSize != 0)
{
WmipProcessEventBuffer(ActiveBuffer,
ReturnSize,
&PrimaryBuffer,
&PrimaryBufferSize,
&BackupBuffer,
&BackupBufferSize,
TRUE);
//
// In the case that we are shutting down the event
// pump and the buffer passed to clear out all of
// the events was too small we need to call back
// down to the kernel to get the rest of the events
// since we cannot exit the thread with events that
// are not delivered. The kernel will not set the
// flag that a new thread is needed unless the irp
// clears all outstanding events
//
} else {
WmipAssert(WmipIsPumpStopping());
if (WmipIsPumpStopping())
{
//
// The irp just completed should have not only
// just cleared all events out of kernel mode
// but also setup flags that new events should
// cause a new pump thread to be created. So
// there may be a new pump thread already created
// Also note there could be yet
// another event pump thread that was created
// if a GN was added or removed. Once we set
// the pump state to IDLE we are off to the
// races (See code at top of function)
//
WmipEnterPMCritSection();
WmipPumpState = EVENT_PUMP_IDLE;
WmipDebugPrint(("WMI: [%x - %x] Pump entered IDLE\n",
GetCurrentProcessId(), GetCurrentThreadId()));
WmipEventBuffer1 = PrimaryBuffer;
WmipEventBufferSize1 = PrimaryBufferSize;
WmipEventBuffer2 = BackupBuffer;
WmipEventBufferSize2 = BackupBufferSize;
//
// Before shutting down the pump we need to
// close any handles that are pending closure
//
WmipClosePendingHandles();
WmipLeavePMCritSection();
return(0);
}
}
} else {
//
// For some reason the request failed. All we can do is
// wait a bit and hope that the problem will clear up.
// If we are stopping the thread we still need to wait
// and try again as all events may not have been
// cleared from the kernel. We really don't know if the
// irp even made it to the kernel.
//
WmipDebugPrint(("WMI: [%x - %x] Error %d from Ioctl\n",
GetCurrentProcessId(), GetCurrentThreadId(),
Status));
Sleep(250);
}
//
// Flag that there is no longer a request outstanding
//
ActiveOverlapped = NULL;
} else if (WaitStatus == STATUS_TIMEOUT) {
//
// The wait for events timed out so we go into the thread
// stopping state to indicate that we are going to terminate
// the thread once all events are cleared out of kernel. At
// this point we are commited to stopping the thread. If any
// GN are added/removed after going into the stopping state,
// a new (and suspended) thread will be created. Right
// before exiting we check if that thread is pending and if
// so resume it.
//
WmipEnterPMCritSection();
WmipDebugPrint(("WMI: [%x - %x] Pump thread entering EVENT_PUMP_STOPPING\n",
GetCurrentProcessId(), GetCurrentThreadId()));
WmipPumpState = EVENT_PUMP_STOPPING;
WmipLeavePMCritSection();
}
}
//
// Should never break out of infinite loop
//
WmipAssert(FALSE);
return(0);
}
#if _MSC_FULL_VER >= 13008827
#pragma warning(pop)
#endif
ULONG WmipEventPumpFromKernel(
PVOID Param
)
{
//
// Note that we MUST call ExitThread when we want to shutdown the
// thread and not return() since the thread has been created by
// kernel mode and there is nothing on the stack to return to, so
// we'd just AV
//
//
// Call into ntdll so that it marks our thread as a CSR thread
//
CsrNewThread();
WmipEnterPMCritSection();
if ((WmipNewPumpThreadPending == FALSE) &&
(WmipPumpState == EVENT_PUMP_IDLE) ||
(WmipPumpState == EVENT_PUMP_STOPPING))
{
//
// If the pump is currently idle or stopping and there is not
// another pump thread that is pending we want our thread
// to be the one that gets the pump going again. We mark the
// that there is a pump thread pending which means that no more
// pump threads will be created when adding/removing GN
// and any pump threads created by kernel will just exit quickly
//
WmipNewPumpThreadPending = TRUE;
WmipDebugPrint(("WMI: [%x - %x] KM pump thread created and pump state now START PENDING\n",
GetCurrentProcessId(), GetCurrentThreadId()));
WmipLeavePMCritSection();
//
// ISSUE: We cannot call WmipEventPump with Param (ie, the
// parameter that is passed to this function) because when the
// thread is created by a Win64 kernel on a x86 app running
// under win64, Param is not actually passed on the stack since
// the code that creates the context forgets to do so
//
ExitThread(WmipEventPump(0));
}
WmipDebugPrint(("WMI: [%x - %x] KM pump thread exiting since pump state %d, %s\n",
GetCurrentProcessId(), GetCurrentThreadId(),
WmipPumpState,
(WmipNewPumpThreadPending == TRUE) ?
"Pump Thread Pending" : "No Pump Thread Pending"));
WmipLeavePMCritSection();
ExitThread(0);
}
ULONG WmipEstablishEventPump(
)
{
#if DBG
#define INITIALEVENTBUFFERSIZE 0x38
#else
#define INITIALEVENTBUFFERSIZE 0x1000
#endif
HANDLE ThreadHandle;
ULONG ThreadId;
ULONG Status;
BOOL b;
#if DBG
//
// On checked builds update the length of time to wait before a
// pump thread times out
//
WmipGetRegistryValue(PumpTimeoutRegValueText,
&WmipEventNotificationWait);
#endif
//
// Make sure the event pump thread is running. We check both the
// pump state and that the device handle is not created since there
// is a window after the handle is created and the thread starts
// running and changes the pump state
//
WmipEnterPMCritSection();
if ((WmipPumpState == EVENT_PUMP_ZERO) &&
(WmipEventDeviceHandle == NULL))
{
//
// Not only is pump not running, but the resources for it
// haven't been allocated
//
WmipAssert(WmipPumpCommandEvent == NULL);
WmipAssert(WmipMyProcessHandle == NULL);
WmipAssert(WmipOverlapped1.hEvent == NULL);
WmipAssert(WmipOverlapped2.hEvent == NULL);
WmipAssert(WmipEventBuffer1 == NULL);
WmipAssert(WmipEventBuffer2 == NULL);
//
// Preallocate all of the resources that the event pump will need
// so that it has no excuse to fail
//
WmipEventDeviceHandle = CreateFile(WMIDataDeviceName,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED,
NULL);
if (WmipEventDeviceHandle == INVALID_HANDLE_VALUE)
{
Status = GetLastError();
goto Cleanup;
}
WmipPumpCommandEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (WmipPumpCommandEvent == NULL)
{
Status = GetLastError();
goto Cleanup;
}
b = DuplicateHandle(GetCurrentProcess(),
GetCurrentProcess(),
GetCurrentProcess(),
&WmipMyProcessHandle,
0,
FALSE,
DUPLICATE_SAME_ACCESS);
if (! b)
{
Status = GetLastError();
goto Cleanup;
}
WmipOverlapped1.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (WmipOverlapped1.hEvent == NULL)
{
Status = GetLastError();
goto Cleanup;
}
WmipOverlapped2.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (WmipOverlapped2.hEvent == NULL)
{
Status = GetLastError();
goto Cleanup;
}
WmipEventBuffer1 = WmipAlloc(INITIALEVENTBUFFERSIZE);
if (WmipEventBuffer1 == NULL)
{
Status = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
WmipEventBufferSize1 = INITIALEVENTBUFFERSIZE;
WmipEventBuffer2 = WmipAlloc(INITIALEVENTBUFFERSIZE);
if (WmipEventBuffer2 == NULL)
{
Status = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
WmipEventBufferSize2 = INITIALEVENTBUFFERSIZE;
ThreadHandle = CreateThread(NULL,
0,
WmipEventPump,
NULL,
0,
&ThreadId);
if (ThreadHandle != NULL)
{
WmipNewPumpThreadPending = TRUE;
WmipDebugPrint(("WMI: [%x] Created initiail pump thread %x\n",
GetCurrentProcessId(), ThreadId
));
CloseHandle(ThreadHandle);
} else {
//
// Since we were able to allocate all of our pump
// resources, but didn't get the pump thread started,
// we will hang onto our resources and move the pump
// state to idle. In this way when the pump is started
// again we do not have to reallocate our resources
//
WmipPumpState = EVENT_PUMP_IDLE;
Status = GetLastError();
goto Done;
}
WmipLeavePMCritSection();
return(ERROR_SUCCESS);
} else {
//
// Pump resources should already be allocated
//
WmipAssert(WmipPumpCommandEvent != NULL);
WmipAssert(WmipMyProcessHandle != NULL);
WmipAssert(WmipOverlapped1.hEvent != NULL);
WmipAssert(WmipOverlapped2.hEvent != NULL);
WmipAssert(WmipEventBuffer1 != NULL);
WmipAssert(WmipEventBuffer2 != NULL);
if ((WmipNewPumpThreadPending == FALSE) &&
(WmipPumpState == EVENT_PUMP_STOPPING) ||
(WmipPumpState == EVENT_PUMP_IDLE))
{
//
// If pump is stopping or is idle then we need to fire up a
// new thread
//
ThreadHandle = CreateThread(NULL,
0,
WmipEventPump,
NULL,
0,
&ThreadId);
if (ThreadHandle != NULL)
{
WmipDebugPrint(("WMI: [%x] Created new pump thread %x (%d %d)\n",
GetCurrentProcessId(), ThreadId,
WmipPumpState, WmipNewPumpThreadPending
));
WmipNewPumpThreadPending = TRUE;
CloseHandle(ThreadHandle);
} else {
Status = GetLastError();
goto Done;
}
} else {
WmipAssert((WmipPumpState == EVENT_PUMP_RUNNING) ||
(WmipNewPumpThreadPending == TRUE));
}
WmipLeavePMCritSection();
return(ERROR_SUCCESS);
}
Cleanup:
if (WmipEventDeviceHandle != NULL)
{
CloseHandle(WmipEventDeviceHandle);
WmipEventDeviceHandle = NULL;
}
if (WmipPumpCommandEvent != NULL)
{
CloseHandle(WmipPumpCommandEvent);
WmipPumpCommandEvent = NULL;
}
if (WmipMyProcessHandle != NULL)
{
CloseHandle(WmipMyProcessHandle);
WmipMyProcessHandle = NULL;
}
if (WmipOverlapped1.hEvent != NULL)
{
CloseHandle(WmipOverlapped1.hEvent);
WmipOverlapped1.hEvent = NULL;
}
if (WmipOverlapped2.hEvent != NULL)
{
CloseHandle(WmipOverlapped2.hEvent);
WmipOverlapped2.hEvent = NULL;
}
if (WmipEventBuffer1 != NULL)
{
WmipFree(WmipEventBuffer1);
WmipEventBuffer1 = NULL;
}
if (WmipEventBuffer2 != NULL)
{
WmipFree(WmipEventBuffer2);
WmipEventBuffer2 = NULL;
}
Done:
WmipLeavePMCritSection();
return(Status);
}
ULONG WmipAddHandleToEventPump(
LPGUID Guid,
PVOID DeliveryInfo,
ULONG_PTR DeliveryContext,
ULONG NotificationFlags,
HANDLE GuidHandle
)
{
ULONG Status;
Status = WmipAddToGNList(Guid,
DeliveryInfo,
DeliveryContext,
NotificationFlags,
GuidHandle);
if (Status == ERROR_SUCCESS)
{
Status = WmipEstablishEventPump();
if (Status == ERROR_SUCCESS)
{
WmipSendPumpCommand();
} else {
//
// If we couldn't establish the event pump we want to
// remove the handle from the GNList and propogate back the
// error
//
WmipRemoveFromGNList(Guid,
DeliveryInfo);
}
} else {
//
// If handle could not be added to the lists then we need to
// close the handle to prevent leaks.
//
CloseHandle(GuidHandle);
}
return(Status);
}
ULONG
WmipNotificationRegistration(
IN LPGUID InGuid,
IN BOOLEAN Enable,
IN PVOID DeliveryInfo,
IN ULONG_PTR DeliveryContext,
IN ULONG64 LoggerContext,
IN ULONG Flags,
IN BOOLEAN IsAnsi
)
{
HANDLE GuidHandle;
GUID Guid;
PVOID NotificationDeliveryContext;
PVOID NotificationDeliveryInfo;
ULONG NotificationFlags;
ULONG Status;
HANDLE ThreadHandle;
DWORD ThreadId;
ULONG ReturnSize;
WmipInitProcessHeap();
//
// Validate input parameters and flags
//
if (InGuid == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return(ERROR_INVALID_PARAMETER);
}
try
{
Guid = *InGuid;
} except(EXCEPTION_EXECUTE_HANDLER) {
SetLastError(ERROR_INVALID_PARAMETER);
return(ERROR_INVALID_PARAMETER);
}
if (Flags == NOTIFICATION_CHECK_ACCESS)
{
//
// Caller just wants to check that he has does have permission
// to enable the notification
//
#ifdef MEMPHIS
return(ERROR_SUCCESS);
#else
Status = WmipCheckGuidAccess(&Guid, WMIGUID_NOTIFICATION);
SetLastError(Status);
return(Status);
#endif
}
//
// Validate that flags are correct
//
if (Enable)
{
if ((Flags != NOTIFICATION_TRACE_FLAG) &&
(Flags != NOTIFICATION_CALLBACK_DIRECT))
{
//
// Invalid Flags were passed
Status = ERROR_INVALID_PARAMETER;
} else if (Flags == NOTIFICATION_TRACE_FLAG) {
Status = ERROR_SUCCESS;
} else if ((Flags == NOTIFICATION_CALLBACK_DIRECT) &&
(DeliveryInfo == NULL)) {
//
// Not a valid callback function
Status = ERROR_INVALID_PARAMETER;
} else {
Status = ERROR_SUCCESS;
}
if (Status != ERROR_SUCCESS)
{
SetLastError(Status);
return(Status);
}
}
NotificationDeliveryInfo = (PVOID)DeliveryInfo;
NotificationDeliveryContext = (PVOID)DeliveryContext;
NotificationFlags = IsAnsi ? DCREF_FLAG_ANSI : 0;
if (Flags & NOTIFICATION_TRACE_FLAG)
{
//
// This is a tracelog enable/disable request so send it down the
// fast lane to KM so it can be processed.
//
WMITRACEENABLEDISABLEINFO TraceEnableInfo;
TraceEnableInfo.Guid = Guid;
TraceEnableInfo.LoggerContext = LoggerContext;
TraceEnableInfo.Enable = Enable;
Status = WmipSendWmiKMRequest(NULL,
IOCTL_WMI_ENABLE_DISABLE_TRACELOG,
&TraceEnableInfo,
sizeof(WMITRACEENABLEDISABLEINFO),
NULL,
0,
&ReturnSize,
NULL);
} else {
//
// This is a WMI event enable/disable event request so fixup the
// flags and send a request off to the event pump thread.
//
if (Flags & NOTIFICATION_CALLBACK_DIRECT) {
NotificationFlags |= NOTIFICATION_FLAG_CALLBACK_DIRECT;
} else {
NotificationFlags |= Flags;
}
if (Enable)
{
//
// Since we are enabling, make sure we have access to the
// guid and then make sure we can get the notification pump
// thread running.
//
Status = WmipOpenKernelGuid(&Guid,
WMIGUID_NOTIFICATION,
&GuidHandle,
IOCTL_WMI_OPEN_GUID_FOR_EVENTS);
if (Status == ERROR_SUCCESS)
{
Status = WmipAddHandleToEventPump(&Guid,
DeliveryInfo,
DeliveryContext,
NotificationFlags |
NOTIFICATION_FLAG_GROUPED_EVENT,
GuidHandle);
}
} else {
Status = WmipRemoveFromGNList(&Guid,
DeliveryInfo);
if (Status == ERROR_SUCCESS)
{
WmipSendPumpCommand();
}
if (Status == ERROR_INVALID_PARAMETER)
{
CHAR s[MAX_PATH];
WmipDebugPrint(("WMI: Invalid DeliveryInfo %x passed to unregister for notification %s\n",
DeliveryInfo,
GuidToStringA(s, &Guid)));
Status = ERROR_WMI_ALREADY_DISABLED;
}
}
}
SetLastError(Status);
return(Status);
}