/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

   enabdisa.c

Abstract:

    Enable and disable code

--*/

#include "wmikmp.h"
#include "tracep.h"

BOOLEAN
WmipIsISFlagsSet(
    PBGUIDENTRY GuidEntry,
    ULONG Flags
    );

NTSTATUS WmipDeliverWnodeToDS(
    CHAR ActionCode, 
    PBDATASOURCE DataSource,
    PWNODE_HEADER Wnode,
    ULONG BufferSize
   );

NTSTATUS WmipSendEnableDisableRequest(
    UCHAR ActionCode,
    PBGUIDENTRY GuidEntry,
    BOOLEAN IsEvent,
    BOOLEAN IsTraceLog,
    ULONG64 LoggerContext
    );

void WmipReleaseCollectionEnabled(
    PBGUIDENTRY GuidEntry
    );

void WmipWaitForCollectionEnabled(
    PBGUIDENTRY GuidEntry
    );

ULONG WmipSendEnableRequest(
    PBGUIDENTRY GuidEntry,
    BOOLEAN IsEvent,
    BOOLEAN IsTraceLog,
    ULONG64 LoggerContext
    );

ULONG WmipDoDisableRequest(
    PBGUIDENTRY GuidEntry,
    BOOLEAN IsEvent,
    BOOLEAN IsTraceLog,
    ULONG64 LoggerContext,
    ULONG InProgressFlag
    );

ULONG WmipSendDisableRequest(
    PBGUIDENTRY GuidEntry,
    BOOLEAN IsEvent,
    BOOLEAN IsTraceLog,
    ULONG64 LoggerContext
    );

NTSTATUS WmipEnableCollectOrEvent(
    PBGUIDENTRY GuidEntry,
    ULONG Ioctl,
    BOOLEAN *RequestSent,
    ULONG64 LoggerContext
    );

NTSTATUS WmipDisableCollectOrEvent(
    PBGUIDENTRY GuidEntry,
    ULONG Ioctl,
    ULONG64 LoggerContext
    );

NTSTATUS WmipEnableDisableTrace(
    IN ULONG Ioctl,
    IN PWMITRACEENABLEDISABLEINFO TraceEnableInfo
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,WmipIsISFlagsSet)
#pragma alloc_text(PAGE,WmipDeliverWnodeToDS)
#pragma alloc_text(PAGE,WmipSendEnableDisableRequest)
#pragma alloc_text(PAGE,WmipReleaseCollectionEnabled)
#pragma alloc_text(PAGE,WmipWaitForCollectionEnabled)
#pragma alloc_text(PAGE,WmipSendEnableRequest)
#pragma alloc_text(PAGE,WmipDoDisableRequest)
#pragma alloc_text(PAGE,WmipSendDisableRequest)
#pragma alloc_text(PAGE,WmipEnableCollectOrEvent)
#pragma alloc_text(PAGE,WmipDisableCollectOrEvent)
#pragma alloc_text(PAGE,WmipEnableDisableTrace)
#pragma alloc_text(PAGE,WmipDisableTraceProviders)
#endif

BOOLEAN
WmipIsISFlagsSet(
    PBGUIDENTRY GuidEntry,
    ULONG Flags
    )
/*++

Routine Description:

    This routine determines whether any of the instance sets associated
    with the GuidEntry has ALL of the flags set

Arguments:

    GuidEntry  Pointer to the Guid Entry structure.
        
    Flags has flags required

Return Value:


--*/
{
    PLIST_ENTRY InstanceSetList;
    PBINSTANCESET InstanceSet;

    PAGED_CODE();
    
    if (GuidEntry != NULL)
    {
        WmipEnterSMCritSection();
        InstanceSetList = GuidEntry->ISHead.Flink;
        while (InstanceSetList != &GuidEntry->ISHead)
        {
            InstanceSet = CONTAINING_RECORD(InstanceSetList,
                                            INSTANCESET,
                                            GuidISList);
            if ( (InstanceSet->Flags & Flags) == Flags )
            {
                WmipLeaveSMCritSection();
                return (TRUE);
            }
            InstanceSetList = InstanceSetList->Flink;
        }
        WmipLeaveSMCritSection();
    }
    return (FALSE);
}

NTSTATUS WmipDeliverWnodeToDS(
    CHAR ActionCode, 
    PBDATASOURCE DataSource,
    PWNODE_HEADER Wnode,
    ULONG BufferSize
   )
{
    NTSTATUS Status;
    IO_STATUS_BLOCK Iosb;
    PWMIGUIDOBJECT GuidObject;

    PAGED_CODE();
    
    if (DataSource->Flags & DS_KERNEL_MODE)
    {    
        //
        // If KM provider then send an irp
        //
        Status = WmipSendWmiIrp(ActionCode,
                                DataSource->ProviderId,
                                &Wnode->Guid,
                                BufferSize,
                                Wnode,
                                &Iosb);
    } else if (DataSource->Flags & DS_USER_MODE) {
        //
        // If UM provider then send a MB message
        //
        GuidObject = DataSource->RequestObject;
        if (GuidObject != NULL)
        {
            Wnode->Flags |= WNODE_FLAG_INTERNAL;
            Wnode->ProviderId = ActionCode;
            Wnode->CountLost = GuidObject->Cookie;
            WmipEnterSMCritSection();
            Status = WmipWriteWnodeToObject(GuidObject,
                                            Wnode,
                                            TRUE);
            WmipLeaveSMCritSection();
        } else {
            Status = STATUS_SUCCESS;
        }
    } else {
        ASSERT(FALSE);
        Status = STATUS_UNSUCCESSFUL;
    }
                     
    return(Status);
}

NTSTATUS WmipSendEnableDisableRequest(
    UCHAR ActionCode,
    PBGUIDENTRY GuidEntry,
    BOOLEAN IsEvent,
    BOOLEAN IsTraceLog,
    ULONG64 LoggerContext
    )
/*++

Routine Description:

    This routine will deliver an event or collection WNODE to all data
    providers of a guid. This routine assumes that it is called with the
    SM critical section held. The routine does not hold the critical
    section for the duration of the call.

Arguments:

    ActionCode is WMI_ENABLE_EVENTS, WMI_DISABLE_EVENTS,
        WMI_ENABLE_COLLECTION or WMI_DISABLE_COLLECTION

    GuidEntry is the guid entry for the guid that is being enabled/disable
        or collected/stop collected

    IsEvent is TRUE then ActionCode is to enable or disable events.
        If FALSE then ActionCode is to enable or disbale collecton

    IsTraceLog is TRUE then enable is only sent to those guids registered as
        being a tracelog guid

    LoggerContext is a logger context handle that should be placed in the
        HistoricalContext field of the WNODE_HEADER if IsTraceLog is TRUE.

Return Value:

    ERROR_SUCCESS or an error code

--*/
{
#if DBG
#define AVGISPERGUID 1
#else
#define AVGISPERGUID 64
#endif

    PLIST_ENTRY InstanceSetList;
    PBINSTANCESET InstanceSet;
    PBDATASOURCE DataSourceArray[AVGISPERGUID];
    PBDATASOURCE *DataSourceList;
    ULONG BufferSize;
    ULONG Status = 0;
    PWNODE_HEADER pWnode;
    ULONG i;
    PBDATASOURCE DataSource;
    ULONG DSCount;
    BOOLEAN IsEnable;
    ULONG IsFlags, IsUpdate;

    WMITRACE_NOTIFY_HEADER  TraceNotifyHeader;

    PAGED_CODE();

    if (GuidEntry->Flags & GE_FLAG_INTERNAL)
    {
        //
        // Guids that have been unregistered and Internally defined guids
        // have no data source to send requests to, so just leave happily
        return(STATUS_SUCCESS);
    }
            

    IsEnable = ((ActionCode == IRP_MN_ENABLE_EVENTS) ||
                (ActionCode == IRP_MN_ENABLE_COLLECTION));
    IsFlags = IsEvent ? IS_ENABLE_EVENT : IS_ENABLE_COLLECTION;

    //
    // Determine whether this is an update call and reset the bit
    //
    IsUpdate = (GuidEntry->Flags & GE_NOTIFICATION_TRACE_UPDATE);


    //
    // First we make a list of all of the DataSources that need to be called
    // while we have the critical section and take a reference on them so
    // they don't go away after we release them. Note that the DataSource
    // structure will stay, but the actual data provider may in fact go away.
    // In this case sending the request will fail.
    DSCount = 0;

    if (GuidEntry->ISCount > AVGISPERGUID)
    {
        DataSourceList = WmipAlloc(GuidEntry->ISCount * sizeof(PBDATASOURCE));
        if (DataSourceList == NULL)
        {
            WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: alloc failed for DataSource array in WmipSendEnableDisableRequest\n"));

            return(STATUS_INSUFFICIENT_RESOURCES);
        }
    } else {
        DataSourceList = &DataSourceArray[0];
    }
#if DBG
    memset(DataSourceList, 0, GuidEntry->ISCount * sizeof(PBDATASOURCE));
#endif

    InstanceSetList = GuidEntry->ISHead.Flink;
    while ((InstanceSetList != &GuidEntry->ISHead) &&
           (DSCount < GuidEntry->ISCount))
    {
        WmipAssert(DSCount < GuidEntry->ISCount);
        InstanceSet = CONTAINING_RECORD(InstanceSetList,
                                        INSTANCESET,
                                        GuidISList);


        //
        // We send requests to those data providers that are not inprocs when
        // it is an event being enabled or it is collection being enabled
        // and they are defined to be expensive (collection needs to be
        // enabled)
        if (
             ( (IsTraceLog && (InstanceSet->Flags & IS_TRACED)) ||
               ( ! IsTraceLog && (! (InstanceSet->Flags & IS_TRACED)) &&
                 (IsEvent || (InstanceSet->Flags & IS_EXPENSIVE))
               )
             )
           )
        {

            if ( (! IsEnable && (InstanceSet->Flags & IsFlags)) ||
                 ((IsEnable && ! (InstanceSet->Flags & IsFlags)) ||
                 (IsUpdate && IsTraceLog))
               )
            {
                DataSourceList[DSCount] = InstanceSet->DataSource;
                WmipReferenceDS(DataSourceList[DSCount]);
                DSCount++;
            }

            if (IsEnable)
            {
                InstanceSet->Flags |= IsFlags;
            } else {
                InstanceSet->Flags &= ~IsFlags;
            }
        }

        InstanceSetList = InstanceSetList->Flink;
    }


    if (IsUpdate) 
    { 
        GuidEntry->Flags &= ~GE_NOTIFICATION_TRACE_UPDATE;
    }


    WmipLeaveSMCritSection();

    //
    // Now without the critical section we send the request to all of the
    // data providers. Any new data providers who register after we made our
    // list will be enabled by the registration code.
    if (DSCount > 0)
    {
        pWnode = &TraceNotifyHeader.Wnode;
        RtlZeroMemory(pWnode, sizeof(TraceNotifyHeader));
        RtlCopyMemory(&pWnode->Guid, &GuidEntry->Guid, sizeof(GUID));
        BufferSize = sizeof(WNODE_HEADER);

        if (IsTraceLog)
        {
            BufferSize = sizeof(TraceNotifyHeader);
            TraceNotifyHeader.LoggerContext = LoggerContext;
            pWnode->Flags |= WNODE_FLAG_TRACED_GUID;
            //
            // If this GUID is already enabled then this must 
            // an update call. So mark it so. 
            // 
            if ( IsEnable &&  IsUpdate ) {
                pWnode->ClientContext = IsUpdate;
            }

        }
        pWnode->BufferSize = BufferSize;

        for (i = 0; i < DSCount; i++)
        {
            DataSource = DataSourceList[i];
            WmipAssert(DataSource != NULL);
            if (IsTraceLog) {
                if (DataSource->Flags & DS_KERNEL_MODE) {
                    pWnode->HistoricalContext = LoggerContext;
                }
                else if (DataSource->Flags & DS_USER_MODE) {
                    pWnode->HistoricalContext = 0;
                }
                else {
                    ASSERT(FALSE);
                }
            }
                                
            Status |= WmipDeliverWnodeToDS(ActionCode, 
                                          DataSource, 
                                          pWnode,
                                          BufferSize);
            

            WmipUnreferenceDS(DataSource);
        }
    }

    if( ! IsTraceLog )
    {

        Status = STATUS_SUCCESS;
    }

    if (DataSourceList != DataSourceArray)
    {
        WmipFree(DataSourceList);
    }

    WmipEnterSMCritSection();

    return(Status);
}

void WmipReleaseCollectionEnabled(
    PBGUIDENTRY GuidEntry
    )
{
    PAGED_CODE();
    
    if (GuidEntry->Flags & GE_FLAG_WAIT_ENABLED)
    {
        WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p enable releasning %p.%p %x event %p\n",
                                 PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                                     GuidEntry,
                                     GuidEntry->Flags));
                                 
        KeSetEvent(GuidEntry->CollectInProgress, 0, FALSE);
        WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p enable did release %p %x event %p\n",
                                 PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                                     GuidEntry,
                                     GuidEntry->Flags));
                                 
        GuidEntry->Flags &= ~GE_FLAG_WAIT_ENABLED;
    }
}

void WmipWaitForCollectionEnabled(
    PBGUIDENTRY GuidEntry
    )
{
    PAGED_CODE();
    
    WmipAssert((GuidEntry->Flags & GE_FLAG_COLLECTION_IN_PROGRESS) ==
                   GE_FLAG_COLLECTION_IN_PROGRESS);
    
    //
    // Collection Enable/Disable is in progress so
    // we cannot return just yet. Right now there could be a 
    // disable request being processed and if we didn't wait, we
    // might get back to this caller before that disable request
    // got around to realizing that it needs to send and enable 
    // request (needed by this thread's caller). So we'd have a 
    // situation where a thread though that collection was enabled
    // but in reality it wasn't yet enabled.
    if ((GuidEntry->Flags & GE_FLAG_WAIT_ENABLED) == 0)
    {
        KeInitializeEvent(GuidEntry->CollectInProgress, 
                          NotificationEvent,
                          FALSE);
        GuidEntry->Flags |= GE_FLAG_WAIT_ENABLED;
        WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p for %p %x created event\n",
                                 PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                                 GuidEntry,
                                 GuidEntry->Flags));
    }
            
    WmipLeaveSMCritSection();
    WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p waiting for %p %x on event\n",
                                 PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                                     GuidEntry,
                                     GuidEntry->Flags));
    KeWaitForSingleObject(GuidEntry->CollectInProgress, 
                          Executive,
                          KernelMode,
                          FALSE,
                          NULL);
    WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p done %p %x waiting on event\n",
                                 PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                                     GuidEntry,
                                     GuidEntry->Flags));
    WmipEnterSMCritSection();
    
}

ULONG WmipSendEnableRequest(
    PBGUIDENTRY GuidEntry,
    BOOLEAN IsEvent,
    BOOLEAN IsTraceLog,
    ULONG64 LoggerContext
    )
/*++
Routine Description:

    This routine will send an enable collection or notification request to
    all of the data providers that have registered the guid being enabled.
    This routine will manage any race conditions that might occur when
    multiple threads are enabling and disabling the notification
    simultaneously.

    This routine is called while the SM critical section is being held and
    will increment the appropriate reference count. if the ref count
    transitions from 0 to 1 then the enable request will need to be forwarded
    to the data providers otherwise the routine is all done and returns.
    Before sending the enable request the routine checks to see if any
    enable or disable requests are currently in progress and if not then sets
    the in progress flag, releases the critical section and sends the enable
    request. If there was a request in progress then the routine does not
    send a request, but just returns. When the other thread that was sending
    the request returns from processing the request it will recheck the
    refcount and notice that it is greater than 0 and then send the enable
    request.


Arguments:

    GuidEntry is the guid entry that describes the guid being enabled. For
        a notification it may be NULL.

    NotificationContext is the notification context to use if enabling events

    IsEvent is TRUE if notifications are being enables else FALSE if
        collection is being enabled

    IsTraceLog is TRUE if enable is for a trace log guid

    LoggerContext is a context value to forward in the enable request

Return Value:

    ERROR_SUCCESS or an error code
--*/
{
    ULONG InProgressFlag;
    ULONG RefCount;
    ULONG Status;

    PAGED_CODE();
    
    if (IsEvent)
    {
        InProgressFlag = GE_FLAG_NOTIFICATION_IN_PROGRESS;
        RefCount = GuidEntry->EventRefCount++;
    } else {
        InProgressFlag = GE_FLAG_COLLECTION_IN_PROGRESS;
        RefCount = GuidEntry->CollectRefCount++;
        WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p enable collect for %p %x\n",
                  PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                  GuidEntry, GuidEntry->Flags ));
    }

    //
    // If the guid is transitioning from a refcount of 0 to 1 and there
    // is not currently a request in progress, then we need to set the
    // request in progress flag, release the critical section and
    // send an enable request. If there is a request in progress we can't
    // do another request. Whenever the current request finishes  it
    // will notice the ref count change and send the enable request on
    // our behalf.
    if ((RefCount == 0) &&
        ! (GuidEntry->Flags & InProgressFlag)) 
    {
        //
        // Take an extra ref count so that even if this gets disabled
        // while the enable request is in progress the GuidEntry
        // will stay valid.
        WmipReferenceGE(GuidEntry);
        GuidEntry->Flags |= InProgressFlag;
        WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p NE %p flags -> %x at %d\n",
                  PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                  GuidEntry,
                  GuidEntry->Flags,
                  __LINE__));

EnableNotification:
        Status = WmipSendEnableDisableRequest((UCHAR)(IsEvent ?
                                                IRP_MN_ENABLE_EVENTS :
                                                IRP_MN_ENABLE_COLLECTION),
                                              GuidEntry,
                                              IsEvent,
                                              IsTraceLog,
                                              LoggerContext);

       RefCount = IsEvent ? GuidEntry->EventRefCount :
                            GuidEntry->CollectRefCount;

       if (RefCount == 0)
       {
           // This is the bogus situation we were worried about. While
           // the enable request was being processed the notification
           // was disabled. So leave the in progress flag set and
           // send the disable.

           Status = WmipSendEnableDisableRequest((UCHAR)(IsEvent ?
                                                    IRP_MN_DISABLE_EVENTS :
                                                    IRP_MN_DISABLE_COLLECTION),
                                                 GuidEntry,
                                                 IsEvent,
                                                 IsTraceLog,
                                                 LoggerContext);

            RefCount = IsEvent ? GuidEntry->EventRefCount :
                                 GuidEntry->CollectRefCount;

            if (RefCount > 0)
            {
                //
                // We have hit a pathological case. One thread called to
                // enable and while the enable request was being processed
                // another thread called to disable, but was postponed
                // since the enable was in progress. So once the enable
                // completed we realized that the ref count reached 0 and
                // so we need to disable and sent the disable request.
                // But while the disable request was being processed
                // an enable request came in so now we need to enable
                // the notification.
                goto EnableNotification;
            }
        }
        GuidEntry->Flags &= ~InProgressFlag;
        WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p NE %p flags -> %x at %d\n",
                  PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                  GuidEntry,
                  GuidEntry->Flags,
                  __LINE__));
        
        //
        // If there are any other threads that were waiting until all of 
        // the enable/disable work completed, we close the event handle
        // to release them from their wait.
        //
        if (! IsEvent)
        {            
            WmipReleaseCollectionEnabled(GuidEntry);
        }

        //
        // Get rid of extra ref count we took above. Note that the
        // GuidEntry could be going away here if there was a
        // disable while the enable was in progress.
        WmipUnreferenceGE(GuidEntry);

    } else if (IsTraceLog && (GuidEntry->Flags & GE_NOTIFICATION_TRACE_UPDATE) ) {
        //
        // If it's a tracelog and we have a trace Update enable call, ignore the 
        // refcount and send it through. 
        //

        WmipReferenceGE(GuidEntry);

        Status = WmipSendEnableDisableRequest((UCHAR)(IsEvent ?
                                                IRP_MN_ENABLE_EVENTS :
                                                IRP_MN_ENABLE_COLLECTION),
                                              GuidEntry,
                                              IsEvent,
                                              IsTraceLog,
                                              LoggerContext);
        GuidEntry->EventRefCount--;

        WmipUnreferenceGE(GuidEntry);

    } else {
        if ((! IsEvent) && (GuidEntry->Flags & InProgressFlag))
        {
            WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p going to wait for %p %x at %d\n",
                                          PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                                          GuidEntry,
                                          GuidEntry->Flags,
                                          __LINE__));
            WmipWaitForCollectionEnabled(GuidEntry);
            WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p done to wait for %p %x at %d\n",
                                          PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                                          GuidEntry,
                                          GuidEntry->Flags,
                                          __LINE__));
            
        }
        
        Status = STATUS_SUCCESS;
    }

    if (! IsEvent)
    {
        WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p enable collect done for %p %x\n",
                  PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                  GuidEntry,
                  GuidEntry->Flags));
    }

    return(Status);
}

ULONG WmipDoDisableRequest(
    PBGUIDENTRY GuidEntry,
    BOOLEAN IsEvent,
    BOOLEAN IsTraceLog,
    ULONG64 LoggerContext,
    ULONG InProgressFlag
    )
{
    ULONG RefCount;
    ULONG Status;

    PAGED_CODE();
    
DisableNotification:
    Status = WmipSendEnableDisableRequest((UCHAR)(IsEvent ?
                                            IRP_MN_DISABLE_EVENTS :
                                            IRP_MN_DISABLE_COLLECTION),
                                          GuidEntry,
                                          IsEvent,
                                          IsTraceLog,
                                          LoggerContext);

    RefCount = IsEvent ? GuidEntry->EventRefCount :
                         GuidEntry->CollectRefCount;

    if (RefCount > 0)
    {
        //
        // While we were processing the disable request an
        // enable request arrived. Since the in progress
        // flag was set the enable request was not sent
        // so now we need to do that.

        Status = WmipSendEnableDisableRequest((UCHAR)(IsEvent ?
                                                 IRP_MN_ENABLE_EVENTS :
                                                 IRP_MN_ENABLE_COLLECTION),
                                              GuidEntry,
                                              IsEvent,
                                              IsTraceLog,
                                              LoggerContext);

        RefCount = IsEvent ? GuidEntry->EventRefCount:
                             GuidEntry->CollectRefCount;

        if (RefCount == 0)
        {
            //
            // While processing the enable request above the
            // notification was disabled and since a request
            // was in progress the disable request was not
            // forwarded. Now it is time to forward the
            // request.
            goto DisableNotification;
        }
    }
    GuidEntry->Flags &= ~InProgressFlag;
    WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p NE %p flags -> %x at %d\n",
                  PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                  GuidEntry,
                  GuidEntry->Flags,
                  __LINE__));
    
    //
    // If there are any other threads that were waiting until all of 
    // the enable/disable work completed, we close the event handle
    // to release them from their wait.
    //
    if (! IsEvent)
    {
        WmipReleaseCollectionEnabled(GuidEntry);
    }
    
    return(Status);
}

ULONG WmipSendDisableRequest(
    PBGUIDENTRY GuidEntry,
    BOOLEAN IsEvent,
    BOOLEAN IsTraceLog,
    ULONG64 LoggerContext
    )
/*++
Routine Description:

    This routine will send an disable collection or notification request to
    all of the data providers that have registered the guid being disabled.
    This routine will manage any race conditions that might occur when
    multiple threads are enabling and disabling the notification
    simultaneously.

    This routine is called while the SM critical section is being held and
    will increment the appropriate reference count. if the ref count
    transitions from 1 to 0 then the disable request will need to be forwarded
    to the data providers otherwise the routine is all done and returns.
    Before sending the disable request the routine checks to see if any
    enable or disable requests are currently in progress and if not then sets
    the in progress flag, releases the critical section and sends the disable
    request. If there was a request in progress then the routine does not
    send a request, but just returns. When the other thread that was sending
    the request returns from processing the request it will recheck the
    refcount and notice that it is  0 and then send the disable
    request.


Arguments:

    GuidEntry is the Notification entry that describes the guid
        being enabled.

    GuidEntry is the guid entry that describes the guid being enabled. For
        a notification it may be NULL.

    NotificationContext is the notification context to use if enabling events

    IsEvent is TRUE if notifications are being enables else FALSE if
        collection is being enabled

    IsTraceLog is TRUE if enable is for a trace log guid

    LoggerContext is a context value to forward in the enable request

Return Value:

    ERROR_SUCCESS or an error code
--*/
{
    ULONG InProgressFlag;
    ULONG RefCount;
    ULONG Status;

    PAGED_CODE();
    
    if (IsEvent)
    {
        InProgressFlag = GE_FLAG_NOTIFICATION_IN_PROGRESS;
        RefCount = GuidEntry->EventRefCount;
        if (RefCount == 0)
        {
            //
            // A bad data consumer is disabling his event more
            // than once. Just ignore it
            return(STATUS_SUCCESS);
        }

        RefCount = --GuidEntry->EventRefCount;
    } else {
        WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p Disabling for %p %x\n",
                                 PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                                 GuidEntry,
                                 GuidEntry->Flags));
        InProgressFlag = GE_FLAG_COLLECTION_IN_PROGRESS;
        RefCount = --GuidEntry->CollectRefCount;
        WmipAssert(RefCount != 0xffffffff);
    }

    //
    // If we have transitioned to a refcount of zero and there is
    // not a request in progress then forward the disable request.
    if ((RefCount == 0) &&
        ! (GuidEntry->Flags & InProgressFlag))
    {

        //
        // Take an extra ref count so that even if this gets
        // disabled while the disable request is in progress the
        // GuidEntry will stay valid.
        GuidEntry->Flags |= InProgressFlag;
        WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p NE %p flags -> %x at %d\n",
                  PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                  GuidEntry,
                  GuidEntry->Flags,
                  __LINE__));

        Status = WmipDoDisableRequest(GuidEntry,
                                      IsEvent,
                                      IsTraceLog,
                                      LoggerContext,
                                      InProgressFlag);
                                  
    } else {
        Status = STATUS_SUCCESS;
    }

    if (! IsEvent)
    {
        WmipDebugPrintEx((DPFLTR_WMICORE_ID, DPFLTR_INFO_LEVEL,"WMI: %p.%p Disable complete for %p %x\n",
                                 PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                                 GuidEntry,
                                 GuidEntry->Flags));
    }
    return(Status);
}


NTSTATUS WmipEnableCollectOrEvent(
    PBGUIDENTRY GuidEntry,
    ULONG Ioctl,
    BOOLEAN *RequestSent,
    ULONG64 LoggerContext
    )
{
    LOGICAL DoEnable;
    BOOLEAN IsEvent, IsTracelog;
    PLIST_ENTRY InstanceSetList;
    PBINSTANCESET InstanceSet;
    NTSTATUS Status;

    PAGED_CODE();
    
    *RequestSent = FALSE;
    
    switch (Ioctl)
    {
        case IOCTL_WMI_OPEN_GUID_FOR_QUERYSET:
        {
            //
            // See if the guid requires an enable collection. Loop over all
            // instance sets that are not for tracelog or events.
            //
            DoEnable = FALSE;
            IsTracelog = FALSE;
            IsEvent = FALSE;
            WmipEnterSMCritSection();
            InstanceSetList = GuidEntry->ISHead.Flink;
            while (InstanceSetList != &GuidEntry->ISHead) 
            {
                InstanceSet = CONTAINING_RECORD(InstanceSetList,
                                        INSTANCESET,
                                        GuidISList);

                if ( ! ((InstanceSet->Flags & IS_TRACED) ||
                        ((InstanceSet->Flags & IS_EVENT_ONLY) && DoEnable)))
                {
                    //
                    // Only those guids not Traced guids, event only guids
                    // and unresolved references are not available for queries
                    DoEnable = (DoEnable || (InstanceSet->Flags & IS_EXPENSIVE));
                }
                InstanceSetList = InstanceSetList->Flink;
            }
        
            WmipLeaveSMCritSection();
            break;
        }
        
        case IOCTL_WMI_OPEN_GUID_FOR_EVENTS:
        {
            //
            // For events we always send enable request
            //
            DoEnable = TRUE;
            IsEvent = TRUE;
            IsTracelog = FALSE;
            //
            // Note: If this guid has GE_NOTIFICATION_TRACE_FLAG set, 
            // then it will get enabled for tracelog as well as for 
            // wmi events. 
            //
            break;
        }
        
        case IOCTL_WMI_ENABLE_DISABLE_TRACELOG:
        {
            //
            // Setup for a tracelog enable request
            //
            DoEnable = TRUE;
            IsEvent = TRUE;
            IsTracelog = TRUE;
            break;
        }
        
        default:
        {
            ASSERT(FALSE);
            return(STATUS_ILLEGAL_FUNCTION);
        }
    }
    
    if (DoEnable)
    {
        WmipEnterSMCritSection();
        Status = WmipSendEnableRequest(GuidEntry,
                              IsEvent,
                              IsTracelog,
                              LoggerContext);
        WmipLeaveSMCritSection();
                          
        if (NT_SUCCESS(Status))
        {
            *RequestSent = TRUE;
        }
    } else {
        Status = STATUS_SUCCESS;
    }
    return(Status);
}

NTSTATUS WmipDisableCollectOrEvent(
    PBGUIDENTRY GuidEntry,
    ULONG Ioctl,
    ULONG64 LoggerContext
    )
{
    BOOLEAN IsEvent, IsTracelog;
    NTSTATUS Status;

    PAGED_CODE();
    
    switch(Ioctl)
    {
        case IOCTL_WMI_OPEN_GUID_FOR_QUERYSET:
        {
            IsEvent = FALSE;
            IsTracelog = FALSE;
            break;
        }
        
        case IOCTL_WMI_OPEN_GUID_FOR_EVENTS:
        {
            //
            // For events we always send enable request
            //    
            IsEvent = TRUE;
            IsTracelog = FALSE;
            break;
        }
            
        case IOCTL_WMI_ENABLE_DISABLE_TRACELOG:
        {
            IsEvent = TRUE;
            IsTracelog = TRUE;
            break;
        }
        
        default:
        {
            ASSERT(FALSE);
            return(STATUS_ILLEGAL_FUNCTION);
        }
            
    }
    
    WmipEnterSMCritSection();
    Status = WmipSendDisableRequest(GuidEntry,
                              IsEvent,
                              IsTracelog,
                              LoggerContext);
    WmipLeaveSMCritSection();

    return(Status);
}

NTSTATUS WmipEnableDisableTrace(
    IN ULONG Ioctl,
    IN PWMITRACEENABLEDISABLEINFO TraceEnableInfo
    )
/*++

Routine Description:

    This routine will enable or disable a tracelog guid

Arguments:

   Ioctl is the IOCTL used to call this routine from UM
         
   TraceEnableInfo has all the info needed to enable or disable

Return Value:


--*/
{
    NTSTATUS Status;
    LPGUID Guid;
    PBGUIDENTRY GuidEntry;
    BOOLEAN RequestSent;
    BOOLEAN IsEnable;
    ULONG64 LoggerContext;
    
    PAGED_CODE();
    
    Guid = &TraceEnableInfo->Guid;
    
    Status = WmipCheckGuidAccess(Guid,
                                 TRACELOG_GUID_ENABLE,
                                 EtwpDefaultTraceSecurityDescriptor);

                
    if (NT_SUCCESS(Status))
    {

        //
        // The following code is serialized for Trace Guids. Only one 
        // control application can be enabling or disabling Trace Guids at a time. 
        // Must be taken before SMCritSection is taken. Otherwise deadlocks will result.
        //
        
        WmipEnterTLCritSection();

        IsEnable = TraceEnableInfo->Enable;

        //
        // Check for Heap and Crit Sec Tracing Guid.
        //

        if( IsEqualGUID(&HeapGuid,Guid)) {

            if(IsEnable){

	            SharedUserData->TraceLogging |= ENABLEHEAPTRACE;

                //
                // increment counter. The counter  
                // is composed of first two bytes
                //

                SharedUserData->TraceLogging += 0x00010000; 


            } else {

                SharedUserData->TraceLogging &= DISABLEHEAPTRACE;
            }

			WmipLeaveTLCritSection();
			return STATUS_SUCCESS;
        } else if(IsEqualGUID(&CritSecGuid,Guid)){  

            if(IsEnable) {

	            SharedUserData->TraceLogging |= ENABLECRITSECTRACE;

                //
                // increment counter. The counter  
                // is composed of first two bytes
                //

                SharedUserData->TraceLogging += 0x00010000; 

            } else {

                SharedUserData->TraceLogging &= DISABLECRITSECTRACE;
            }

			WmipLeaveTLCritSection();
			return STATUS_SUCCESS;

        } else if(IsEqualGUID(&NtdllTraceGuid,Guid)){  

            if(!IsEnable){

                SharedUserData->TraceLogging &= DISABLENTDLLTRACE;

            }
        }

        LoggerContext = TraceEnableInfo->LoggerContext;
        
        WmipEnterSMCritSection();

        GuidEntry = WmipFindGEByGuid(Guid, FALSE);
        
        if (GuidEntry == NULL )
        {
            //
            // The guid is not yet registered
            //
            if (IsEnable )
            {
                //
                // If the NtdllTraceGuid is not in list then we do not want to enable it
                // the NtdllTraceGuid will make an entry only to call starttrace
                //

                if(IsEqualGUID(&NtdllTraceGuid,Guid)){

                    Status = STATUS_ILLEGAL_FUNCTION;

                } else {

                    //
                    // If we are enabling a guid that is not yet registered
                    // we need to create the guid object for it
                    //

                    GuidEntry = WmipAllocGuidEntry();
                    if (GuidEntry != NULL)
                    {
                        //
                        // Initialize the guid entry and keep the ref count
                        // from creation. When tracelog enables we take a ref
                        // count and when it disables we release it
                        //
                        GuidEntry->Guid = *Guid;
                        GuidEntry->Flags |= GE_NOTIFICATION_TRACE_FLAG;
                        GuidEntry->LoggerContext = LoggerContext;
                        GuidEntry->EventRefCount = 1; 
                        InsertHeadList(WmipGEHeadPtr, &GuidEntry->MainGEList);
                        Status = STATUS_SUCCESS;                    
                    } else {
                        Status = STATUS_INSUFFICIENT_RESOURCES;
                    }
                }
            } 

        } else {
            //
            // The control guid is already registered so lets go and
            // enabled or disable it
            //
            if (WmipIsControlGuid(GuidEntry))
            {
                if (IsEnable)
                {
                    GuidEntry->LoggerContext = LoggerContext;
                    if (GuidEntry->Flags & GE_NOTIFICATION_TRACE_FLAG)
                    {
                        //
                        // We are trying to enable a trace guid that is already
                        // registered
                        //
                        GuidEntry->Flags |= GE_NOTIFICATION_TRACE_UPDATE;
                        Status = WmipEnableCollectOrEvent(GuidEntry,
                                             Ioctl,
                                             &RequestSent,
                                             LoggerContext);

                    } else {
                        GuidEntry->Flags |= GE_NOTIFICATION_TRACE_FLAG;
                        WmipReferenceGE(GuidEntry);

                        Status = WmipEnableCollectOrEvent(GuidEntry,
                                             Ioctl,
                                             &RequestSent,
                                             LoggerContext);
                        
                        if (!NT_SUCCESS(Status) &&
                            (GuidEntry->Flags & GE_NOTIFICATION_TRACE_FLAG)) {

                            //
                            // We failed to enable the trace event, and our
                            // flag is still set. Remove the flag and deref
                            // the guid entry.
                            //
                            // When we call WmipEnableCollectOrEvent above,
                            // we actually drop the SM lock for a little bit.
                            // That means it's possible for a disable call to
                            // come through at the same time, which would
                            // remove the flag and deref the guid entry itself.
                            //

                            GuidEntry->Flags &= ~GE_NOTIFICATION_TRACE_FLAG;
                            WmipUnreferenceGE(GuidEntry);
                        }
                    }

                } else {

                    if (GuidEntry->Flags & GE_NOTIFICATION_TRACE_FLAG)
                    {
                        //
                        // Send the disable collection call and then remove
                        // the refcount that was taken when we enabled
                        //
                        GuidEntry->Flags &= ~GE_NOTIFICATION_TRACE_FLAG;
                        Status = WmipDisableCollectOrEvent(GuidEntry,
                                                 Ioctl,
                                                 LoggerContext);
                        //
                        // Whether the Disable request succeeds or not
                        // we will remove the extra refcount since we 
                        // reset the NOTIFICATION_FLAG
                        //
                        GuidEntry->LoggerContext = 0;
                        WmipUnreferenceGE(GuidEntry);
                    } else {
                        Status = STATUS_WMI_ALREADY_DISABLED;
                    }
                }
            } else if ( IsListEmpty(&GuidEntry->ISHead)  && (! IsEnable) ) {
                //
                // If this GUID is not a control GUID, check to see if 
                // there are no instance sets for this GUID. If so, 
                // it is getting disabled before any instances 
                // registered it. Disable the GUID and clean up the GE. 
                //
                if (GuidEntry->Flags & GE_NOTIFICATION_TRACE_FLAG)
                {
                    GuidEntry->Flags &= ~GE_NOTIFICATION_TRACE_FLAG;
                    GuidEntry->LoggerContext = 0;
                    WmipUnreferenceGE(GuidEntry);
                }
                Status = STATUS_SUCCESS;

            } else if(!IsEqualGUID(&NtdllTraceGuid,Guid)){

                Status = STATUS_ILLEGAL_FUNCTION;

            }

            WmipUnreferenceGE(GuidEntry);
        }

        WmipLeaveSMCritSection();

        WmipLeaveTLCritSection();
    }
    return(Status);
}


//
// When a Logger is shutdown, all providers logging to this logger
// are notified to stop logging first.
//

NTSTATUS 
WmipDisableTraceProviders (
    ULONG StopLoggerId
    )
{
    PBGUIDENTRY GuidEntry;
    PLIST_ENTRY GuidEntryList;
    ULONG LoggerId;
    NTSTATUS Status = STATUS_SUCCESS;

    PAGED_CODE();

    //
    // Find all the providers that are logging to this logger
    // and disable them automatically.
    //
CheckAgain:

    WmipEnterSMCritSection();

    GuidEntryList = WmipGEHeadPtr->Flink;
    while (GuidEntryList != WmipGEHeadPtr)
    {
        GuidEntry = CONTAINING_RECORD(GuidEntryList,
                                     GUIDENTRY,
                                     MainGEList);

        if (GuidEntry->Flags & GE_NOTIFICATION_TRACE_FLAG)
        {
            LoggerId = WmiGetLoggerId(GuidEntry->LoggerContext);
            if (LoggerId == StopLoggerId) {
                //
                // Send Disable Notification
                //
                WmipReferenceGE(GuidEntry);
                GuidEntry->Flags &= ~GE_NOTIFICATION_TRACE_FLAG;
                Status = WmipSendDisableRequest(GuidEntry,
                          TRUE,
                          TRUE,
                          GuidEntry->LoggerContext);
                //
                // Since we reset the NOTIFICATION_TRACE_FLAG
                // we will take out the extra ref count whether the 
                // SendDisableRequest was successful or not. 
                //
                GuidEntry->LoggerContext = 0;
                WmipUnreferenceGE(GuidEntry);

                //
                // We need to remove the refcount that was taken when this
                // Guid was Enabled. 
                //

                WmipUnreferenceGE(GuidEntry);

                WmipLeaveSMCritSection();

                //
                // We have to jump out and restart the loop because we let go 
                // of the critsect during SendDisableRequest call
                //
                goto CheckAgain;

            }
        }
        GuidEntryList = GuidEntryList->Flink;
    }

    WmipLeaveSMCritSection();

    return Status;
}

