text: /*++
  
  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:
  
      cmnotify.c
  
  Abstract:
  
      This module contains support for NtNotifyChangeKey.
  
  --*/
  
  
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //                                                                                                                      //
  //   "The" POST BLOCK RULE :                                                                                            //
  //                                                                                                                      //
  //      To operate on a post block (i.e. add or remove it from a list - notify,thread,slave),                           //
  //      you should at least:                                                                                            //
  //          1. Hold the registry lock exclusively                                                                       //
  //                     OR                                                                                               //
  //          2. Hold the registry lock shared and acquire the postblock mutex.                                            //
  //                                                                                                                      //
  //                                                                                                                      //
  //      WARNING!!!                                                                                                      //
  //          Failing to do that could arise in obscure registry deadlocks or usage of already freed memory (bugcheck)    //
  //                                                                                                                      //
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //                                                                                                                      //
  //  Other important rules to follow:                                                                                    //
  //                                                                                                                      //
  //      1. We DO NOT dereference objects in CmpPostApc !                                                                //
  //      2. We DO NOT dereference objects while walking the notify list!                                                 //
  //      3. All operations with Thread PostList are done in CmpPostApc or at APC level. This should avoid two threads    //
  //          operating on the same list at the same time                                                                 //
  //                                                                                                                      //
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  
  #include    "cmp.h"
  
  #define CmpCheckPostBlock(a) //nothing
  
  //
  // "Back Side" of notify
  //
  
  extern  PCMHIVE  CmpMasterHive;
  
  VOID
  CmpReportNotifyHelper(
      PCM_KEY_CONTROL_BLOCK KeyControlBlock,
      IN PHHIVE SearchHive,
      IN PHHIVE Hive,
      IN HCELL_INDEX Cell,
      IN ULONG Filter
      );
  
  VOID
  CmpCancelSlavePost(
      PCM_POST_BLOCK  PostBlock,
      PLIST_ENTRY     DelayedDeref
      );
  
  VOID
  CmpFreeSlavePost(
      PCM_POST_BLOCK  MasterPostBlock
      );
  
  VOID
  CmpAddToDelayedDeref(
      PCM_POST_BLOCK  PostBlock,
      PLIST_ENTRY     DelayedDeref
      );
  
  VOID
  CmpDelayedDerefKeys(
                      PLIST_ENTRY DelayedDeref
                      );
  
  BOOLEAN
  CmpNotifyTriggerCheck(
      IN PCM_NOTIFY_BLOCK NotifyBlock,
      IN PHHIVE Hive,
      IN PCM_KEY_NODE Node
      );
  
  VOID
  CmpDummyApc(
      struct _KAPC *Apc,
      PVOID *SystemArgument1,
      PVOID *SystemArgument2
      );
  
  #ifdef ALLOC_PRAGMA
  #pragma alloc_text(PAGE,CmpReportNotify)
  #pragma alloc_text(PAGE,CmpReportNotifyHelper)
  #pragma alloc_text(PAGE,CmpPostNotify)
  #pragma alloc_text(PAGE,CmpPostApc)
  #pragma alloc_text(PAGE,CmpPostApcRunDown)
  #pragma alloc_text(PAGE,CmNotifyRunDown)
  #pragma alloc_text(PAGE,CmpFlushNotify)
  #pragma alloc_text(PAGE,CmpNotifyChangeKey)
  #pragma alloc_text(PAGE,CmpCancelSlavePost)
  #pragma alloc_text(PAGE,CmpFreeSlavePost)
  #pragma alloc_text(PAGE,CmpAddToDelayedDeref)
  #pragma alloc_text(PAGE,CmpDelayedDerefKeys)
  #pragma alloc_text(PAGE,CmpNotifyTriggerCheck)
  #pragma alloc_text(PAGE,CmpDummyApc)
  #endif
  
  VOID
  CmpDummyApc(
      struct _KAPC *Apc,
      PVOID *SystemArgument1,
      PVOID *SystemArgument2
      )
  /*++
  
  Routine Description:
  
      Dummy routine to prevent user-mode callers to set special kernel apcs
  
  Arguments:
  
      Apc - pointer to apc object
  
      SystemArgument1 -  IN: Status value for IoStatusBlock
                        OUT: Ptr to IoStatusBlock (2nd arg to user apc routine)
  
      SystemArgument2 - Pointer to the PostBlock
  
  Return Value:
  
      NONE.
  
  --*/
  {
      UNREFERENCED_PARAMETER(Apc);
      UNREFERENCED_PARAMETER(SystemArgument1);
      UNREFERENCED_PARAMETER(SystemArgument2);
  }
  
  VOID
  CmpReportNotify(
      PCM_KEY_CONTROL_BLOCK   KeyControlBlock,
      PHHIVE                  Hive,
      HCELL_INDEX             Cell,
      ULONG                   Filter
      )
  /*++
  
  Routine Description:
  
      This routine is called when a notifiable event occurs. It will
      apply CmpReportNotifyHelper to the hive the event occured in,
      and the master hive if different.
  
  Arguments:
  
      KeyControlBlock - KCB of the key at which the event occured.
              For create or delete this is the created or deleted key.
  
      Hive - pointer to hive containing cell of Key at which event occured.
  
      Cell - cell of Key at which event occured
  
              (hive and cell correspond with name.)
  
      Filter - event to be reported
  
  Return Value:
  
      NONE.
  
  --*/
  {
      HCELL_INDEX     CellToRelease = HCELL_NIL;
  
      CM_PAGED_CODE();
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"CmpReportNotify:\n"));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"\tHive:%p Cell:%08lx Filter:%08lx\n", Hive, Cell, Filter));
  
      //
      // If the operation was create or delete, treat it as a change
      // to the parent.
      //
      if (Filter == REG_NOTIFY_CHANGE_NAME) {
          PCM_KEY_NODE pcell;
          ULONG       flags;
  
          pcell = (PCM_KEY_NODE)HvGetCell(Hive, Cell);
          if( pcell == NULL ) {
              //
              // we couldn't map the bin containing this cell
              // Bad luck! notifications will be broken.
              //
              return;
          }
          
          CellToRelease = Cell;
  
          flags = pcell->Flags;
          Cell = pcell->Parent;
          if (flags & KEY_HIVE_ENTRY) {
              ASSERT( CellToRelease != HCELL_NIL );
              HvReleaseCell(Hive,CellToRelease);
  
              Hive = &(CmpMasterHive->Hive);
              pcell = (PCM_KEY_NODE)HvGetCell(Hive, Cell);
              if( pcell == NULL ) {
                  //
                  // we couldn't map the bin containing this cell
                  // Bad luck! notifications will be broken.
                  //
                  return;
              }
              CellToRelease = Cell;
          }
  
  
          KeyControlBlock = KeyControlBlock->ParentKcb;
  
          //
          // if we're at an exit/link node, back up the real node
          // that MUST be it's parent.
          //
          if (pcell->Flags & KEY_HIVE_EXIT) {
              Cell = pcell->Parent;
          }
  
          ASSERT( CellToRelease != HCELL_NIL );
          HvReleaseCell(Hive,CellToRelease);
  
      }
  
      //
      // Report to notifies waiting on the event's hive
      //
      CmpReportNotifyHelper(KeyControlBlock, Hive, Hive, Cell, Filter);
  
  
      //
      // If containing hive is not the master hive, apply to master hive
      //
      if (Hive != &(CmpMasterHive->Hive)) {
          CmpReportNotifyHelper(KeyControlBlock,
                                &(CmpMasterHive->Hive),
                                Hive,
                                Cell,
                                Filter);
      }
  
      return;
  }
  
  BOOLEAN
  CmpNotifyTriggerCheck(
      IN PCM_NOTIFY_BLOCK NotifyBlock,
      IN PHHIVE Hive,
      IN PCM_KEY_NODE Node
      )
  /*++
  
  Routine Description:
  
      Checks if a notify can be triggered
  
  Arguments:
  
      NotifyBlock - the notify block
  
      Hive - Supplies hive containing node to match with.
  
      Node - pointer to key to match with (and check access to)
  
  
  Return Value:
  
      TRUE - yes.
      FALSE - no
  
  --*/
  {
      PCM_POST_BLOCK PostBlock;
      POST_BLOCK_TYPE NotifyType;
  
      CM_PAGED_CODE();
  
      if(IsListEmpty(&(NotifyBlock->PostList)) == FALSE) {
  
          //
          // check if it is a kernel notify. Look at the first post block
          // to see that. If is a kernel post-block, then all posts in 
          // the list should be kernel notifies
          //
          PostBlock = (PCM_POST_BLOCK)NotifyBlock->PostList.Flink;
          PostBlock = CONTAINING_RECORD(PostBlock,
                                        CM_POST_BLOCK,
                                        NotifyList);
  
          NotifyType = PostBlockType(PostBlock);
  
          if( NotifyType == PostAsyncKernel ) {
              // this is a kernel notify; always trigger it
  #if DBG
              //
              // DEBUG only code: All post blocks should be of the same type
              // (kernel/user)
              //
              while( PostBlock->NotifyList.Flink != &(NotifyBlock->PostList) ) {
                  PostBlock = (PCM_POST_BLOCK)PostBlock->NotifyList.Flink;
                  PostBlock = CONTAINING_RECORD(PostBlock,
                                              CM_POST_BLOCK,
                                              NotifyList);
                  
                  CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"CmpNotifyTriggerCheck : NotifyBlock = %p\n",NotifyBlock));
                  
                  ASSERT( PostBlockType(PostBlock) == NotifyType );
              }
  #endif
          
              return TRUE;
          }
      }
  
      //
      // else, check if the caller has the right access
      //
      return CmpCheckNotifyAccess(NotifyBlock,Hive,Node);
  }
  
  VOID
  CmpReportNotifyHelper(
      PCM_KEY_CONTROL_BLOCK KeyControlBlock,
      IN PHHIVE SearchHive,
      IN PHHIVE Hive,
      IN HCELL_INDEX Cell,
      IN ULONG Filter
      )
  /*++
  
  Routine Description:
  
      Scan the list of active notifies for the specified hive.  For
      any with scope including KeyControlBlock and filter matching
      Filter, and with proper security access, post the notify.
  
  Arguments:
  
      Name - canonical path name (as in a key control block) of the key
              at which the event occured.  (This is the name for
              reporting purposes.)
  
      SearchHive - hive to search for matches (which notify list to check)
  
      Hive - Supplies hive containing node to match with.
  
      Cell - cell identifying the node in Hive
  
      Filter - type of event
  
  Return Value:
  
      NONE.
  
  --*/
  {
      PLIST_ENTRY         NotifyPtr;
      PCM_NOTIFY_BLOCK    NotifyBlock;
      PCMHIVE             CmSearchHive;
      KIRQL               OldIrql;
      LIST_ENTRY          DelayedDeref;
      PCM_KEY_NODE        Node;
  
      CM_PAGED_CODE();
  
      Node = (PCM_KEY_NODE)HvGetCell(Hive,Cell);
      if( Node == NULL ) {
          //
          // bad luck, we cannot map the view containing this cell
          //
          return;
      }
  
      CmSearchHive = CONTAINING_RECORD(SearchHive, CMHIVE, Hive);
      CmLockHive(CmSearchHive);
  
      KeRaiseIrql(APC_LEVEL, &OldIrql);
  
      NotifyPtr = &(CmSearchHive->NotifyList);
  
      InitializeListHead(&(DelayedDeref));
  
      while (NotifyPtr->Flink != NULL) {
  
          NotifyPtr = NotifyPtr->Flink;
  
          NotifyBlock = CONTAINING_RECORD(NotifyPtr, CM_NOTIFY_BLOCK, HiveList);
          if (NotifyBlock->KeyControlBlock->TotalLevels > KeyControlBlock->TotalLevels) {
              //
              // list is level sorted, we're past all shorter entries
              //
              break;
          } else {
              PCM_KEY_CONTROL_BLOCK kcb;
              ULONG LevelDiff, l;
  
              LevelDiff = KeyControlBlock->TotalLevels - NotifyBlock->KeyControlBlock->TotalLevels;
  
              kcb = KeyControlBlock;
              for (l=0; l<LevelDiff; l++) {
                  kcb = kcb->ParentKcb;
              }
  
              if (kcb == NotifyBlock->KeyControlBlock) {
                  //
                  // This Notify path is the prefix of this kcb.
                  //
                  if ((NotifyBlock->Filter & Filter)
                              &&
                      ((NotifyBlock->WatchTree == TRUE) ||
                       (Cell == kcb->KeyCell))
                     )
                  {
                      // Filter matches, this event is relevant to this notify
                      //                  AND
                      // Either the notify spans the whole subtree, or the cell
                      // (key) of interest is the one it applies to
                      //
                      // THEREFORE:   The notify is relevant.
                      //
  
                      //
                      // Correct scope, does caller have access?
                      //
                      if (CmpNotifyTriggerCheck(NotifyBlock,Hive,Node)) {
                          //
                          // Notify block has KEY_NOTIFY access to the node
                          // the event occured at.  It is relevant.  Therefore,
                          // it gets to see this event.  Post and be done.
                          //
                          // we specify that we want no key body dereferenciation 
                          // during the CmpPostNotify call. This is to prevent the 
                          // deletion of the current notify block
                          //
                          CmpPostNotify(
                              NotifyBlock,
                              NULL,
                              Filter,
                              STATUS_NOTIFY_ENUM_DIR,
                              FALSE,
                              &DelayedDeref
  #if DBG
                              ,(PCMHIVE)CmSearchHive
  #endif 
                              );
  
                      }  // else no KEY_NOTIFY access to node event occured at
                  } // else not relevant (wrong scope, filter, etc)
              }
          }
      }
      
      KeLowerIrql(OldIrql);
      CmUnlockHive(CmSearchHive);
  
      HvReleaseCell(Hive,Cell);
  
      //
      // finish the job started in CmpPostNotify (i.e. dereference the keybodies
      // we prevented. this may cause some notifyblocks to be freed
      //
      CmpDelayedDerefKeys(&DelayedDeref);
  
      return;
  }
  
  VOID
  CmpPostNotify(
      PCM_NOTIFY_BLOCK    NotifyBlock,
      PUNICODE_STRING     Name OPTIONAL,
      ULONG               Filter,
      NTSTATUS            Status,
      BOOLEAN             PostListLockHeld,
      PLIST_ENTRY         ExternalKeyDeref OPTIONAL
  #if DBG
      ,
      PCMHIVE             CmHive
  #endif 
      )
  /*++
  
  Routine Description:
  
      Actually report the notify event by signaling events, enqueing
      APCs, and so forth.
  
      When Status is STATUS_NOTIFY_CLEANUP:
  
        - if the post block is a slave one, just cancel it.
        - if the post block is a master one, cancel all slave post blocks
          and trigger event on the master block.
  
  Comments:
      
      This routine is using a "delayed dereferencing" technique to prevent
      deadlocks that may appear when a keybody is dereferenced while holding
      the post block lock. As for this, a list with keybodies that have to be 
      dereferenced is constructed while walking the list of postblocks attached
      to the current notify block and the related (slave or master) post blocks.
      The list is built by tricking postblocks. For all postblock about to be 
      freed the PostKeyBody member is added to the local list and then set to NULL
      on the postblock. This will avoid the key body dereferencing in CmpFreePostBlock.
      Instead, after the postblock lock is released, the local list is iterated and 
      the keybodies are dereferenced and the storage for associated CM_POST_KEY_BODY 
      objects is freed.
  
    
  Arguments:
  
      NotifyBlock - pointer to structure that describes the notify
                    operation.  (Where to post to)
  
      Name - name of key at which event occurred.
  
      Filter - nature of event
  
      Status - completion status to report
  
      ExternalKeyDeref - this parameter (when not NULL) specifies that the caller doesn't 
                      want any keybody to be dereferenced while in this routine
  
  Return Value:
  
      NONE.
  
  --*/
  {
      PCM_POST_BLOCK      PostBlock;
      PCM_POST_BLOCK      SlavePostBlock;
      LIST_ENTRY          LocalDelayedDeref;
      KIRQL               OldIrql;
      PLIST_ENTRY         DelayedDeref;
  
      Filter;
      Name;
  
      CM_PAGED_CODE();
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"CmpPostNotify:\n"));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"\tNotifyBlock:%p  ", NotifyBlock));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"\tName = %wZ\n", Name));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"\tFilter:%08lx  Status=%08lx\n", Filter, Status));
      ASSERT_CM_LOCK_OWNED();
  
      if( ARGUMENT_PRESENT(ExternalKeyDeref) ) {
          //
          // The caller want to do all keybody dereferencing by himself
          //
          DelayedDeref = ExternalKeyDeref;
      } else {
          // local delayed dereferencing (the caller doesn't care!)
          DelayedDeref = &LocalDelayedDeref;
          InitializeListHead(DelayedDeref);
      }
  
      ASSERT_HIVE_LOCK_OWNED(CmHive);
      //
      // Acquire exclusive access over the postlist(s)
      //
      if( !PostListLockHeld ) {
          LOCK_POST_LIST();
      }
  
      if (IsListEmpty(&(NotifyBlock->PostList)) == TRUE) {
          //
          // Nothing to post, set a mark and return
          //
          NotifyBlock->NotifyPending = TRUE;
          if( !PostListLockHeld ) {
              UNLOCK_POST_LIST();
          }
          return;
      }
      NotifyBlock->NotifyPending = FALSE;
  
      //
      // IMPLEMENTATION NOTE:
      //      If we ever want to actually implement the code that returns
      //      names of things that changed, this is the place to add the
      //      name and operation type to the buffer.
      //
  
      //
      // Pull and post all the entries in the post list
      //
      while (IsListEmpty(&(NotifyBlock->PostList)) == FALSE) {
  
          //
          // Remove from the notify block list, and enqueue the apc.
          // The apc will remove itself from the thread list
          //
          PostBlock = (PCM_POST_BLOCK)RemoveHeadList(&(NotifyBlock->PostList));
          PostBlock = CONTAINING_RECORD(PostBlock,
                                        CM_POST_BLOCK,
                                        NotifyList);
  
          // Protect for multiple deletion of the same object
          CmpClearListEntry(&(PostBlock->NotifyList));
          
          if( (Status == STATUS_NOTIFY_CLEANUP) && !IsMasterPostBlock(PostBlock) ) {
              //
              // Cleanup notification (i.e. the key handle was closed or the key was deleted)
              // When the post is a slave one, just cancel it. Canceling means:
              //      1. Removing from the notify PostList (already done at this point - see above)
              //      2. Unchaining from the Master Block CancelPostList
              //      3. Delisting from the thread PostBlockList
              //      4. Actually freeing the memory
              //
  
              // Use Cmp variant to protect for multiple deletion of the same object
              CmpRemoveEntryList(&(PostBlock->CancelPostList));
              //
              // FIX 289351
              //
              // Use Cmp variant to protect for multiple deletion of the same object
              KeRaiseIrql(APC_LEVEL, &OldIrql);
              CmpRemoveEntryList(&(PostBlock->ThreadList));
              KeLowerIrql(OldIrql);
  
  #if DBG
              if(PostBlock->TraceIntoDebugger) {
                  CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]\tCmpPostNotify: PostBlock:%p is a slave block,and notify is CLEANUP==> just cleanning\n", PostBlock));
              }
  #endif
              if( PostBlock->NotifyType != PostSynchronous ) {
  
                  // add to the deref list and clean the post block
                  CmpAddToDelayedDeref(PostBlock,DelayedDeref);
  
                  //
                  // Front-end routine will do self cleanup for synchronous notifications
                  CmpFreePostBlock(PostBlock);
              }
  
              continue; //try the next one
          }
  
          //
          // Simulate that this block is the master one, so we can free the others
          // Doing that will ensure the right memory deallocation when the master
          // (from now on this block) will be freed.
          //
          if(!IsMasterPostBlock(PostBlock)) {
              //
              // this is not the master block, we have some more work to do
              //
              SlavePostBlock = PostBlock;
              do {
                  SlavePostBlock = (PCM_POST_BLOCK)SlavePostBlock->CancelPostList.Flink;
                  SlavePostBlock = CONTAINING_RECORD(SlavePostBlock,
                                                     CM_POST_BLOCK,
                                                     CancelPostList);
                  //
                  // reset the "master flag" if set
                  //
                  ClearMasterPostBlockFlag(SlavePostBlock);
              } while (SlavePostBlock != PostBlock);
  
              //
              // Make this post block the master one
              //
              SetMasterPostBlockFlag(PostBlock);
          }
  
  #if DBG
          if(PostBlock->TraceIntoDebugger) {
              CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]\tCmpPostNotify: Master block switched to :%p\n", PostBlock));
          }
  #endif
  
          //
          // Cancel all slave Post requests that may be linked to self
          //
  
          if( PostBlockType(PostBlock) != PostSynchronous ) {
              //
              // Front-end routine will do self cleanup for synchronous notifications
              CmpCancelSlavePost(PostBlock,DelayedDeref);
              //
              // Do the same for the master (in case master and slave got switched)
              // This will avoid dereferencing the keybody from CmpPostApc
              CmpAddToDelayedDeref(PostBlock,DelayedDeref);
          }
  
          switch (PostBlockType(PostBlock)) {
              case PostSynchronous:
                  //
                  // This is a SYNC notify call.  There will be no user event,
                  // and no user apc routine.  Quick exit here, just fill in
                  // the Status and poke the event.
                  //
                  // Holder of the systemevent will wake up and free the
                  // postblock.  If we free it here, we get a race & bugcheck.
                  //
                  // Set the flink to NULL so that the front side can tell this
                  // has been removed if its wait aborts.
                  //
                  PostBlock->NotifyList.Flink = NULL;
                  PostBlock->u->Sync.Status = Status;
                  KeSetEvent(PostBlock->u->Sync.SystemEvent,
                             0,
                             FALSE);
                  break;
  
              case PostAsyncUser:
                  //
                  // Insert the APC into the queue
                  //
                  KeInsertQueueApc(PostBlock->u->AsyncUser.Apc,
                                   (PVOID)ULongToPtr(Status),
                                   (PVOID)PostBlock,
                                   0);
                  break;
  
              case PostAsyncKernel:
                  //
                  // Queue the work item, then free the post block.
                  //
                  if (PostBlock->u->AsyncKernel.WorkItem != NULL) {
                      ExQueueWorkItem(PostBlock->u->AsyncKernel.WorkItem,
                                      PostBlock->u->AsyncKernel.QueueType);
                  }
  
                  //
                  // Signal Event if present, and deref it.
                  //
                  if (PostBlock->u->AsyncKernel.Event != NULL) {
                      KeSetEvent(PostBlock->u->AsyncKernel.Event,
                                 0,
                                 FALSE);
                      ObDereferenceObject(PostBlock->u->AsyncKernel.Event);
                  }
  
                  //
                  // Multiple async kernel notification are not allowed
                  //
                  ASSERT(IsListEmpty(&(PostBlock->CancelPostList)) == TRUE);
                  //
                  // remove the post block from the thread list, and free it
                  //
                  // Use Cmp variant to protect for multiple deletion of the same object
                  KeRaiseIrql(APC_LEVEL, &OldIrql);
                  CmpRemoveEntryList(&(PostBlock->ThreadList));
                  KeLowerIrql(OldIrql);
                  
                  // it was already added to delayed deref.
                  CmpFreePostBlock(PostBlock);
                  break;
          }
      }
  
      if( !PostListLockHeld ) {
          UNLOCK_POST_LIST();
      }
  
      //
      // At this point we have a list of keybody elements that have to be dereferenciated
      // and the associated storage for the covering objects freed. The keybodies in this 
      // list have only one reference count on them (they were referenced only in 
      // NtNotifyChangeMultipleKeys), dereferencing them here should free the object
      //
  
      if( ARGUMENT_PRESENT(ExternalKeyDeref) ) {
          // do nothing; the caller wants to handle the dereferenciation by himself!
      } else {
          // dereferenciate all keybodies in the delayed list
          CmpDelayedDerefKeys(DelayedDeref);
      }
     
      return;
  }
  
  
  VOID
  CmpPostApc(
      struct _KAPC *Apc,
      PKNORMAL_ROUTINE *NormalRoutine,
      PVOID *NormalContext,
      PVOID *SystemArgument1,
      PVOID *SystemArgument2
      )
  /*++
  
  Routine Description:
  
      This is the kernel apc routine.  It is called for all notifies,
      regardless of what form of notification the caller requested.
  
      We compute the postblock address from the apc object address.
      IoStatus is set.  SystemEvent and UserEvent will be signaled
      as appropriate.  If the user requested an APC, then NormalRoutine
      will be set at entry and executed when we exit.  The PostBlock
      is freed here.
  
  Arguments:
  
      Apc - pointer to apc object
  
      NormalRoutine - Will be called when we return
  
      NormalContext - will be 1st argument to normal routine, ApcContext
                      passed in when NtNotifyChangeKey was called
  
      SystemArgument1 -  IN: Status value for IoStatusBlock
                        OUT: Ptr to IoStatusBlock (2nd arg to user apc routine)
  
      SystemArgument2 - Pointer to the PostBlock
  
  Return Value:
  
      NONE.
  
  --*/
  {
      PCM_POST_BLOCK  PostBlock;
  
      CM_PAGED_CODE();
  
  #if !DBG
      UNREFERENCED_PARAMETER (Apc);
      UNREFERENCED_PARAMETER (NormalRoutine);
      UNREFERENCED_PARAMETER (NormalContext);
  #endif
  
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"CmpPostApc:\n"));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"\tApc:%p ", Apc));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"NormalRoutine:%p\n", NormalRoutine));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"\tNormalContext:%08lx", NormalContext));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"\tSystemArgument1=IoStatusBlock:%p\n", SystemArgument1));
  
  
      PostBlock = *(PCM_POST_BLOCK *)SystemArgument2;
  
  #if DBG
      if(PostBlock->TraceIntoDebugger) {
          CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]CmpPostApc: PostBlock:%p\n", PostBlock));
      }
  #endif
  
      
      //
      // Fill in IO Status Block
      //
      // IMPLEMENTATION NOTE:
      //      If we ever want to actually implement the code that returns
      //      names of things that changed, this is the place to copy the
      //      buffer into the caller's buffer.
      //
      //  Win64 only: Use a 32bit IO_STATUS_BLOCK if the caller is 32bit.
  
      try {
          CmpCheckIoStatusPointer(PostBlock->u->AsyncUser);
          CmpSetIoStatus(PostBlock->u->AsyncUser.IoStatusBlock, 
                         *((ULONG *)SystemArgument1), 
                         0L,
                         PsGetCurrentProcess()->Wow64Process != NULL);
          CmpCheckIoStatusPointer(PostBlock->u->AsyncUser);
      } except (EXCEPTION_EXECUTE_HANDLER) {
          NOTHING;
      }
      *SystemArgument1 = PostBlock->u->AsyncUser.IoStatusBlock;
      CmpCheckIoStatusPointer(PostBlock->u->AsyncUser);
  
      //
      // This is an Async notify, do all work here, including
      // cleaning up the post block
      //
  
      //
      // Signal UserEvent if present, and deref it.
      //
      if (PostBlock->u->AsyncUser.UserEvent != NULL) {
          KeSetEvent(PostBlock->u->AsyncUser.UserEvent,
                     0,
                     FALSE);
          ObDereferenceObject(PostBlock->u->AsyncUser.UserEvent);
      }
  
      //
      // remove the post block from the thread list, and free it
      //
      // Use Cmp variant to protect for multiple deletion of the same object
      CmpRemoveEntryList(&(PostBlock->ThreadList));
  
      // debug only checks
      CmpCheckPostBlock(PostBlock);
      //
      // Free the slave post block to avoid "dangling" postblocks
      //
      CmpFreeSlavePost(PostBlock);
      //
      // free this post block
      // 
      CmpFreePostBlock(PostBlock);
  
      return;
  }
  
  
  VOID
  CmpPostApcRunDown(
      struct _KAPC *Apc
      )
  /*++
  
  Routine Description:
  
      This routine is called to clear away apcs in the apc queue
      of a thread that has been terminated.
  
      Since the apc is in the apc queue, we know that it is NOT in
      any NotifyBlock's post list.  It is, however, in the threads's
      PostBlockList.
  
      Therefore, poke any user events so that waiters are not stuck,
      drop the references so the event can be cleaned up, delist the
      PostBlock and free it.
  
      Since we are cleaning up the thread, SystemEvents are not interesting.
  
      Since the apc is in the apc queue, we know that if there were any other
      notifications related to this one, they are cleaned up by the
      CmPostNotify routine
  
  Arguments:
  
      Apc - pointer to apc object
  
  Return Value:
  
      NONE.
  
  --*/
  {
      PCM_POST_BLOCK  PostBlock;
      KIRQL           OldIrql;
  
      CM_PAGED_CODE();
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"CmpApcRunDown:"));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"\tApc:%p \n", Apc));
  
      KeRaiseIrql(APC_LEVEL, &OldIrql);
  
      PostBlock = (PCM_POST_BLOCK)Apc->SystemArgument2;
  
  #if DBG
      if(PostBlock->TraceIntoDebugger) {
          CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]CmpPostApcRunDown: PostBlock:%p\n", PostBlock));
      }
  #endif
  
      //
      // report status and wake up any threads that might otherwise
      // be stuck.  also drop any event references we hold
      //
      //  Win64 only: Use a 32bit IO_STATUS_BLOCK if the caller is 32bit. 
  
      try {
          CmpCheckIoStatusPointer(PostBlock->u->AsyncUser);
          CmpSetIoStatus(PostBlock->u->AsyncUser.IoStatusBlock, 
                         STATUS_NOTIFY_CLEANUP, 
                         0L, 
                         PsGetCurrentProcess()->Wow64Process != NULL);
          CmpCheckIoStatusPointer(PostBlock->u->AsyncUser);
      } except (EXCEPTION_EXECUTE_HANDLER) {
          NOTHING;
      }
  
      if (PostBlock->u->AsyncUser.UserEvent != NULL) {
          KeSetEvent(
              PostBlock->u->AsyncUser.UserEvent,
              0,
              FALSE
              );
          ObDereferenceObject(PostBlock->u->AsyncUser.UserEvent);
      }
  
      //
      // delist the post block
      //
      // Use Cmp variant to protect for multiple deletion of the same object
      CmpRemoveEntryList(&(PostBlock->ThreadList));
  
      //
      // Free the slave post block to avoid "dangling" postblocks
      //
      CmpFreeSlavePost(PostBlock);
      //
      // Free the post block.  Use Ex call because PostBlocks are NOT
      // part of the global registry pool computation, but are instead
      // part of NonPagedPool with Quota.
      //
      CmpFreePostBlock(PostBlock);
  
      KeLowerIrql(OldIrql);
  
      return;
  }
  
  
  //
  // Cleanup procedure
  //
  VOID
  CmNotifyRunDown(
      __in PETHREAD    Thread
      )
  /*++
  
  Routine Description:
  
      This routine is called from PspExitThread to clean up any pending
      notify requests.
  
      It will traverse the thread's PostBlockList, for each PostBlock it
      finds, it will:
  
          1.  Remove it from the relevant NotifyBlock.  This requires
              that we hold the Registry mutex.
  
          2.  Remove it from the thread's PostBlockList.  This requires
              that we run at APC level.
  
          3.  By the time this procedure runs, user apcs are not interesting
              and neither are SystemEvents, so do not bother processing
              them.
  
              UserEvents and IoStatusBlocks could be referred to by other
              threads in the same process, or even a different process,
              so process them so those threads know what happened, use
              status code of STATUS_NOTIFY_CLEANUP.
  
              If the notify is a master one, cancel all slave notifications.
              Else only remove this notification from the master CancelPortList
  
          4.  Free the post block.
  
  Arguments:
  
      Thread - pointer to the executive thread object for the thread
               we wish to do rundown on.
  
  Return Value:
  
      NONE.
  
  --*/
  {
      PCM_POST_BLOCK      PostBlock;
      KIRQL               OldIrql;
  
      CM_PAGED_CODE();
  
      if ( IsListEmpty(&(Thread->PostBlockList)) == TRUE ) {
          return;
      }
  
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_FLOW,"CmNotifyRunDown: ethread:%p\n", Thread));
  
      CmpLockRegistry();
      //
      // Acquire exclusive access over the postlist(s)
      //
      LOCK_POST_LIST(); 
  
      KeRaiseIrql(APC_LEVEL, &OldIrql);
      while (IsListEmpty(&(Thread->PostBlockList)) == FALSE) {
  
          //
          // remove from thread list
          //
          PostBlock = (PCM_POST_BLOCK)RemoveHeadList(&(Thread->PostBlockList));
          PostBlock = CONTAINING_RECORD(
                          PostBlock,
                          CM_POST_BLOCK,
                          ThreadList
                          );
  
          // Protect for multiple deletion of the same object
          CmpClearListEntry(&(PostBlock->ThreadList));
  
  #if DBG
          if(PostBlock->TraceIntoDebugger) {
              CmKdPrintEx((DPFLTR_CONFIG_ID,CML_FLOW,"[CM]CmpNotifyRunDown: ethread:%p, PostBlock:%p\n", Thread,PostBlock));
          }
  #endif
  
          //
          // Canceling a master notification implies canceling all the slave notifications
          // from the CancelPostList
          //
          if(IsMasterPostBlock(PostBlock)) {
  
  #if DBG
              if(PostBlock->TraceIntoDebugger) {
                      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_FLOW,"[CM]\tCmpNotifyRunDown: PostBlock:%p is a master block\n", PostBlock));
              }
  #endif
              //
              // at this point, CmpReportNotify and friends will no longer
              // attempt to post this post block.
              //
              if (PostBlockType(PostBlock) == PostAsyncUser) {
                  //
                  // report status and wake up any threads that might otherwise
                  // be stuck.  also drop any event references we hold
                  //
                  //  Win64 only: Use a 32bit IO_STATUS_BLOCK if the caller is 32bit. 
  
                  try {
                      CmpCheckIoStatusPointer(PostBlock->u->AsyncUser);
                      CmpSetIoStatus(PostBlock->u->AsyncUser.IoStatusBlock, 
                                     STATUS_NOTIFY_CLEANUP, 
                                     0L, 
                                     PsGetCurrentProcess()->Wow64Process != NULL);
                      CmpCheckIoStatusPointer(PostBlock->u->AsyncUser);
                  } except (EXCEPTION_EXECUTE_HANDLER) {
                      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_EXCEPTION,"!!CmNotifyRundown: code:%08lx\n", GetExceptionCode()));
                      NOTHING;
                  }
  
                  if (PostBlock->u->AsyncUser.UserEvent != NULL) {
                      KeSetEvent(
                          PostBlock->u->AsyncUser.UserEvent,
                          0,
                          FALSE
                          );
                      ObDereferenceObject(PostBlock->u->AsyncUser.UserEvent);
                  }
  
                  //
                  // Cancel the APC. Otherwise the rundown routine will also
                  // free the post block if the APC happens to be queued at
                  // this point. If the APC is queued, then the post block has
                  // already been removed from the notify list, so don't remove
                  // it again.
                  //
                  if (!KeRemoveQueueApc(PostBlock->u->AsyncUser.Apc)) {
  
                      //
                      // remove from notify block's list
                      //
                      // Use Cmp variant to protect for multiple deletion of the same object
                      CmpRemoveEntryList(&(PostBlock->NotifyList));
                      //
                      // Cancel all slave Post requests that may be linked to self
                      //
                      CmpCancelSlavePost(PostBlock,NULL); // we do not want delayed deref
                  } else {
                      //
                      // if we are here, the apc was in the apc queue, i.e. both master and slave 
                      // post blocks were removed from the notify list. nothing more to do.
                      //
                      ASSERT( CmpIsListEmpty(&(PostBlock->NotifyList)) );
                      NOTHING;
                  }
              } else {
                  //
                  // remove from notify block's list
                  //
                  // Use Cmp variant to protect for multiple deletion of the same object
                  CmpRemoveEntryList(&(PostBlock->NotifyList));
                  //
                  // Cancel all slave Post requests that may be linked to self
                  //
                  CmpCancelSlavePost(PostBlock,NULL); // we do not want delayed deref
              }
  
              //
              // Free the slave Post blocks too
              //
              CmpFreeSlavePost(PostBlock);
              //
              // Free the post block.  Use Ex call because PostBlocks are NOT
              // part of the global registry pool computation, but are instead
              // part of NonPagedPool with Quota.
              //
              CmpFreePostBlock(PostBlock);
          } else {
  
  #if DBG
              if(PostBlock->TraceIntoDebugger) {
                  CmKdPrintEx((DPFLTR_CONFIG_ID,CML_FLOW,"[CM]\tCmpNotifyRunDown: PostBlock:%p is a slave block\n", PostBlock));
              }
  #endif
              //
              // master should always be ahead of slaves; if we got here, we switched master
              // and slaves back in CmpPostNotify; Show some respect and add slave at the end;
              // master will control the cleanup
              //
              ASSERT( CmpIsListEmpty(&(PostBlock->CancelPostList)) == FALSE );
              ASSERT( IsListEmpty(&(Thread->PostBlockList)) == FALSE );
                         
              InsertTailList(
                  &(Thread->PostBlockList),
                  &(PostBlock->ThreadList)
                  );
          }
      }
  
      KeLowerIrql(OldIrql);
  
      UNLOCK_POST_LIST();
  
      CmpUnlockRegistry();
      return;
  }
  
  
  VOID
  CmpFlushNotify(
      PCM_KEY_BODY        KeyBody,
      BOOLEAN             LockHeld
      )
  /*++
  
  Routine Description:
  
      Clean up notifyblock when a handle is closed or the key it refers
      to is deleted.
  
  Arguments:
  
      KeyBody - supplies pointer to key object body for handle we
                  are cleaning up.
  
  Return Value:
  
      NONE
  
  --*/
  {
      PCM_NOTIFY_BLOCK    NotifyBlock;
      PCMHIVE             Hive;
  
      CM_PAGED_CODE();
      ASSERT_CM_LOCK_OWNED();
  
      if (KeyBody->NotifyBlock == NULL) {
          return;
      }
  
      //
      // Lock the hive exclusively to prevent multiple threads from whacking
      // on the list.
      //
      Hive = CONTAINING_RECORD(KeyBody->KeyControlBlock->KeyHive,
                               CMHIVE,
                               Hive);
      if( !LockHeld ) {
          CmLockHive(Hive);
      } else {
          //
          // we should be holding the reglock exclusive
          //
          ASSERT_CM_LOCK_OWNED_EXCLUSIVE();
      }
      //
      // Reread the notify block in case it has already been freed.
      //
      NotifyBlock = KeyBody->NotifyBlock;
      if (NotifyBlock == NULL) {
          if( !LockHeld ) {
              CmUnlockHive(Hive); 
          }
          return;
      }
  
      //
      // Clean up all PostBlocks waiting on the NotifyBlock
      //
      if (IsListEmpty(&(NotifyBlock->PostList)) == FALSE) {
          CmpPostNotify(
              NotifyBlock,
              NULL,
              0,
              STATUS_NOTIFY_CLEANUP,
              FALSE,
              NULL
  #if DBG
              ,Hive
  #endif
              );
      }
  
      //
      // Reread the notify block in case it has already been freed.
      //
      NotifyBlock = KeyBody->NotifyBlock;
      if (NotifyBlock == NULL) {
          if( !LockHeld ) {
              CmUnlockHive(Hive); 
          }
          return;
      }
  
      //
      // Release the subject context
      //
      SeReleaseSubjectContext(&NotifyBlock->SubjectContext);
  
      //
      // IMPLEMENTATION NOTE:
      //      If we ever do code to report names and types of events,
      //      this is the place to free the buffer.
      //
  
      //
      // Remove the NotifyBlock from the hive chain
      //
      NotifyBlock->HiveList.Blink->Flink = NotifyBlock->HiveList.Flink;
      if (NotifyBlock->HiveList.Flink != NULL) {
          NotifyBlock->HiveList.Flink->Blink = NotifyBlock->HiveList.Blink;
      }
  
      // Protect for multiple deletion of the same object
      CmpClearListEntry(&(NotifyBlock->HiveList));
  
      KeyBody->NotifyBlock = NULL;
  
      if( !LockHeld ) {
          CmUnlockHive(Hive); 
      }
  
      //
      // Free the block, clean up the KeyBody
      //
      ExFreePool(NotifyBlock);
      return;
  }
  
  
  //
  // "Front Side" of notify.  See also Ntapi.c: ntnotifychangekey
  //
  NTSTATUS
  CmpNotifyChangeKey(
      IN PCM_KEY_BODY     KeyBody,
      IN PCM_POST_BLOCK   PostBlock,
      IN ULONG            CompletionFilter,
      IN BOOLEAN          WatchTree,
      IN PVOID            Buffer,
      IN ULONG            BufferSize,
      IN PCM_POST_BLOCK   MasterPostBlock
      )
  /*++
  
  Routine Description:
  
      This routine sets up the NotifyBlock, and attaches the PostBlock
      to it.  When it returns, the Notify is visible to the system,
      and will receive event reports.
  
      If there is already an event report pending, then the notify
      call will be satisfied at once.
  
  Arguments:
  
      KeyBody - pointer to key object that handle refers to, allows access
                to key control block, notify block, etc.
  
      PostBlock - pointer to structure that describes how/where the caller
                  is to be notified.
  
                  WARNING:    PostBlock must come from Pool, THIS routine
                              will keep it, back side will free it.  This
                              routine WILL free it in case of error.
  
      CompletionFilter - what types of events the caller wants to see
  
      WatchTree - TRUE to watch whole subtree, FALSE to watch only immediate
                  key the notify is applied to
  
      Buffer - pointer to area to receive notify data
  
      BufferSize - size of buffer, also size user would like to allocate
                   for internal buffer
  
      MasterPostBlock - the post block of the master notification. Used to
                        insert the PostBlock into the CancelPostList list.
  
  OBS:
      POST_BLOCK_LIST is assumed hold on call.
  
  Return Value:
  
      Status.
  
  --*/
  {
      PCM_NOTIFY_BLOCK    NotifyBlock;
      PCM_NOTIFY_BLOCK    node;
      PLIST_ENTRY         ptr;
      PCMHIVE             Hive;
      KIRQL               OldIrql;
  
      CM_PAGED_CODE();
  
      UNREFERENCED_PARAMETER (Buffer);
      UNREFERENCED_PARAMETER (BufferSize);
  
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"CmpNotifyChangeKey:\n"));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"\tKeyBody:%p PostBlock:%p ", KeyBody, PostBlock));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"Filter:%08lx WatchTree:%08lx\n", CompletionFilter, WatchTree));
  
      //
      // The registry lock should be acquired exclusively by the caller !!!
      //
      ASSERT_CM_LOCK_OWNED();
  
      if (KeyBody->KeyControlBlock->Delete) {
          ASSERT( KeyBody->NotifyBlock == NULL );
          CmpFreePostBlock(PostBlock);
          return STATUS_KEY_DELETED;
      }
  
  #if DBG
      if(PostBlock->TraceIntoDebugger) {
          WCHAR                   *NameBuffer = NULL;
          UNICODE_STRING          KeyName;
          PCM_KEY_NODE            TempNode;
  
          CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]CmpNotifyChangeKey: PostBlock:%p\tMasterBlock: %p\n", PostBlock,MasterPostBlock));
          
          TempNode = (PCM_KEY_NODE)HvGetCell(KeyBody->KeyControlBlock->KeyHive, KeyBody->KeyControlBlock->KeyCell);
          if( TempNode != NULL ) {
              NameBuffer = ExAllocatePool(PagedPool, REG_MAX_KEY_NAME_LENGTH);
              if(NameBuffer&& (KeyBody->KeyControlBlock->KeyCell != HCELL_NIL)) {
                 CmpInitializeKeyNameString(TempNode,&KeyName,NameBuffer);
                 CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"\t[CM]CmpNotifyChangeKey: Key = %.*S\n",KeyName.Length / sizeof(WCHAR),KeyName.Buffer));
                 ExFreePool(NameBuffer);
              }
              HvReleaseCell(KeyBody->KeyControlBlock->KeyHive, KeyBody->KeyControlBlock->KeyCell);
          }
      }
  #endif
  
      Hive = (PCMHIVE)KeyBody->KeyControlBlock->KeyHive;
      Hive = CONTAINING_RECORD(Hive, CMHIVE, Hive);
      NotifyBlock = KeyBody->NotifyBlock;
  
      if (NotifyBlock == NULL) {
          //
          // Set up new notify session
          //
          NotifyBlock = ExAllocatePoolWithQuotaTag(PagedPool|POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,sizeof(CM_NOTIFY_BLOCK),CM_NOTIFYBLOCK_TAG);
          CmKdPrintEx((DPFLTR_CONFIG_ID,CML_POOL,"**CmpNotifyChangeKey: allocate:%08lx, ", sizeof(CM_NOTIFY_BLOCK)));
          CmKdPrintEx((DPFLTR_CONFIG_ID,CML_POOL,"type:%d, at:%p\n", PagedPool, NotifyBlock));
  
          if (NotifyBlock == NULL) {
              CmpFreePostBlock(PostBlock);
              return STATUS_INSUFFICIENT_RESOURCES;
          }
          NotifyBlock->KeyControlBlock = KeyBody->KeyControlBlock;
          NotifyBlock->Filter = CompletionFilter;
          NotifyBlock->WatchTree = WatchTree;
          NotifyBlock->NotifyPending = FALSE;
          InitializeListHead(&(NotifyBlock->PostList));
          KeyBody->NotifyBlock = NotifyBlock;
          NotifyBlock->KeyBody = KeyBody;
          ASSERT( KeyBody->KeyControlBlock->Delete == FALSE );
  
  #if DBG
          if(PostBlock->TraceIntoDebugger) {
              WCHAR                   *NameBuffer = NULL;
              UNICODE_STRING          KeyName;
              PCM_KEY_NODE            TempNode;
  
              TempNode = (PCM_KEY_NODE)HvGetCell(KeyBody->KeyControlBlock->KeyHive, KeyBody->KeyControlBlock->KeyCell);
              if( TempNode != NULL ) {
                  NameBuffer = ExAllocatePool(PagedPool, REG_MAX_KEY_NAME_LENGTH);
                  if(NameBuffer) {
                     CmpInitializeKeyNameString(TempNode,&KeyName,NameBuffer);
                     CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]\tCmpNotifyChangeKey: New NotifyBlock at:%p was allocated for Key = %.*S\n",NotifyBlock,KeyName.Length / sizeof(WCHAR),KeyName.Buffer));
                     ExFreePool(NameBuffer);
                  }
                  HvReleaseCell(KeyBody->KeyControlBlock->KeyHive, KeyBody->KeyControlBlock->KeyCell);
              }
          }
  #endif
  
          //
          // IMPLEMENTATION NOTE:
          //      If we ever want to actually return the buffers full of
          //      data, the buffer should be allocated and its address
          //      stored in the notify block here.
          //
  
          //
          // Capture the subject context so we can do checking once the
          // notify goes off.
          //
          SeCaptureSubjectContext(&NotifyBlock->SubjectContext);
  
          //
          // Attach notify block to hive in properly sorted order
          //
          ptr = &(Hive->NotifyList);
          while (TRUE) {
              if (ptr->Flink == NULL) {
                  //
                  // End of list, add self after ptr.
                  //
                  ptr->Flink = &(NotifyBlock->HiveList);
                  NotifyBlock->HiveList.Flink = NULL;
                  NotifyBlock->HiveList.Blink = ptr;
                  break;
              }
  
              ptr = ptr->Flink;
  
              node = CONTAINING_RECORD(ptr, CM_NOTIFY_BLOCK, HiveList);
  
              if (node->KeyControlBlock->TotalLevels >
                  KeyBody->KeyControlBlock->TotalLevels)
              {
                  //
                  // ptr -> notify with longer name than us, insert in FRONT
                  //
                  NotifyBlock->HiveList.Flink = ptr;
                  ptr->Blink->Flink = &(NotifyBlock->HiveList);
                  NotifyBlock->HiveList.Blink = ptr->Blink;
                  ptr->Blink = &(NotifyBlock->HiveList);
                  break;
              }
          }
      }
  
  
      //
      // Add post block to front of notify block's list, and add it to thread list.
      //
      InsertHeadList(
          &(NotifyBlock->PostList),
          &(PostBlock->NotifyList)
          );
  
  
  
      if( IsMasterPostBlock(PostBlock) ) {
          //
          // Protect against outrageous calls
          //
          ASSERT(PostBlock == MasterPostBlock);
  
          //
          // When the notification is a master one, initialize the CancelPostList list
          //
          InitializeListHead(&(PostBlock->CancelPostList));
      } else {
          //
          // Add PostBlock at the end of the CancelPostList list from the master post
          //
          InsertTailList(
              &(MasterPostBlock->CancelPostList),
              &(PostBlock->CancelPostList)
              );
      }
  
  
      KeRaiseIrql(APC_LEVEL, &OldIrql);
      //
      // show some respect and add masters to the front and slaves to the tail
      //
      if( IsMasterPostBlock(PostBlock) ) {
          InsertHeadList(
              &(PsGetCurrentThread()->PostBlockList),
              &(PostBlock->ThreadList)
              );
      } else {
          InsertTailList(
              &(PsGetCurrentThread()->PostBlockList),
              &(PostBlock->ThreadList)
              );
      }
  
  #if DBG
      if(PostBlock->TraceIntoDebugger) {
          CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]\tCmpNotifyChangeKey: Attaching the post:%p\t to thread:%p\n",PostBlock,PsGetCurrentThread()));
      }
  #endif
  
      KeLowerIrql(OldIrql);
  
      //
      // If there is a notify pending (will not be if we just created
      // the notify block) then post it at once.  Note that this call
      // ALWAYS returns STATUS_PENDING unless it fails.  Caller must
      // ALWAYS look in IoStatusBlock to see what happened.
      //
      if (NotifyBlock->NotifyPending == TRUE) {
          CmpPostNotify(
              NotifyBlock,
              NULL,
              0,
              STATUS_NOTIFY_ENUM_DIR,
              TRUE,
              NULL
  #if DBG
              ,Hive
  #endif
              );
  
          //
          // return STATUS_SUCCESS to signal to the caller the the notify already been triggered
          //
          return STATUS_SUCCESS;
      }
  
      //
      // return STATUS_PENDING to signal to the caller the the notify has not been triggered yet
      //
      return STATUS_PENDING;
  }
  
  VOID
  CmpFreeSlavePost(
      PCM_POST_BLOCK  MasterPostBlock
      )
  /*++
  
  Routine Description:
  
      Free the slave post block related to this master post block
  
  Arguments:
  
      MasterPostBlock - pointer to structure that describes the post requests.
                  It should be a master post!!
  Return Value:
  
      NONE.
  
  --*/
  {
      PCM_POST_BLOCK  SlavePostBlock;
  
      CM_PAGED_CODE();
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"CmpCancelSlavePost:\t"));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"MasterPostBlock:%p\n", MasterPostBlock));
  
      ASSERT(IsMasterPostBlock(MasterPostBlock));
  
  #if DBG
      if(MasterPostBlock->TraceIntoDebugger) {
          CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]CmCancelSlavePost: MasterPostBlock:%p\n", MasterPostBlock));
      }
  #endif
  
      if (IsListEmpty(&(MasterPostBlock->CancelPostList)) == TRUE) {
          //
          // Nothing to cancel, just return
          //
  #if DBG
          if(MasterPostBlock->TraceIntoDebugger) {
              CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]CmCancelSlavePost: MasterPostBlock:%p has no slaves\n", MasterPostBlock));
          }
  #endif
  
          return;
      }
  
  
      //
      // Pull all the entries in the cancel post list and unlink them (when they are slave requests)
      // We base here on the assumption that there is only one slave.
      //
      //     NOTE!!!
      //       When more than slave allowed, here to modify
      //
  
  
      SlavePostBlock = (PCM_POST_BLOCK)MasterPostBlock->CancelPostList.Flink;
      SlavePostBlock = CONTAINING_RECORD(SlavePostBlock,
                                         CM_POST_BLOCK,
                                         CancelPostList);
  
  #if DBG
      if(MasterPostBlock->TraceIntoDebugger) {
          CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]CmCancelSlavePost: Cleaning SlavePostBlock:%p\n", SlavePostBlock));
      }
  #endif
  
      //
      // This should be true !
      //
      ASSERT( !IsMasterPostBlock(SlavePostBlock) );
  
      //
      // Unchain from the Master CancelPostList
      //
      // Use Cmp variant to protect for multiple deletion of the same object
      CmpRemoveEntryList(&(SlavePostBlock->CancelPostList));
  
      //
      // delist the post block from the thread postblocklist
      //
      // Use Cmp variant to protect for multiple deletion of the same object
      CmpRemoveEntryList(&(SlavePostBlock->ThreadList));
  
      //
      // Free the post block.
      //
      CmpFreePostBlock(SlavePostBlock);
  
      //
      // Result validation. was it the only slave?
      //
      ASSERT(IsListEmpty(&(MasterPostBlock->CancelPostList)));
  }
  
  VOID
  CmpCancelSlavePost(
      PCM_POST_BLOCK  MasterPostBlock,
      PLIST_ENTRY     DelayedDeref
      )
  /*++
  
  Routine Description:
  
      Unlink the slave postblock from its notify list and dereferences (or adds to the delayed deref list)
      the keybody related to this thread. This should disable the slave post block. 
      It will be cleared later in CmpPostApc.
  
  Arguments:
  
      MasterPostBlock - pointer to structure that describes the post requests.
                  It should be a master post!!
      DelayedDeref - pointer to list of delayed deref keybodies. If this parameter is not NULL,
                  the keybody for the slave is not cleared before calling CmpFreePostBlock, 
                  and instead is added to the list
  
  
  Return Value:
  
      NONE.
  
  --*/
  {
      PCM_POST_BLOCK  SlavePostBlock;
  
      CM_PAGED_CODE();
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"CmpCancelSlavePost:\t"));
      CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"MasterPostBlock:%p\n", MasterPostBlock));
  
      ASSERT_CM_LOCK_OWNED();
  
      ASSERT(IsMasterPostBlock(MasterPostBlock));
  
  #if DBG
      if(MasterPostBlock->TraceIntoDebugger) {
          CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]CmCancelSlavePost: MasterPostBlock:%p\n", MasterPostBlock));
      }
  #endif
  
      if (IsListEmpty(&(MasterPostBlock->CancelPostList)) == TRUE) {
          //
          // Nothing to cancel, just return
          //
  #if DBG
          if(MasterPostBlock->TraceIntoDebugger) {
              CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]CmCancelSlavePost: MasterPostBlock:%p has no slaves\n", MasterPostBlock));
          }
  #endif
  
          return;
      }
  
  
      //
      // Pull all the entries in the cancel post list and unlink them (when they are slave requests)
      // We base here on the assumption that there is only one slave.
      //
      //     NOTE!!!
      //       When more than slave allowed, here to modify
      //
  
  
      SlavePostBlock = (PCM_POST_BLOCK)MasterPostBlock->CancelPostList.Flink;
      SlavePostBlock = CONTAINING_RECORD(SlavePostBlock,
                                         CM_POST_BLOCK,
                                         CancelPostList);
  
  #if DBG
      if(MasterPostBlock->TraceIntoDebugger) {
          CmKdPrintEx((DPFLTR_CONFIG_ID,CML_NOTIFY,"[CM]CmCancelSlavePost: Cleaning SlavePostBlock:%p\n", SlavePostBlock));
      }
  #endif
  
      //
      // This should be true !
      //
      ASSERT( !IsMasterPostBlock(SlavePostBlock) );
  
      //
      // Remove it from notify block's list
      //
      // Use Cmp variant to protect for multiple deletion of the same object
      // This will disable the notifications that might come on the slave key
      //
      CmpRemoveEntryList(&(SlavePostBlock->NotifyList));
  
      if( DelayedDeref ) {
          // 
          // the caller wants to handle key body dereferenciation by himself
          //
          CmpAddToDelayedDeref(SlavePostBlock,DelayedDeref);
      }
  }
  
  VOID
  CmpAddToDelayedDeref(
      PCM_POST_BLOCK  PostBlock,
      PLIST_ENTRY     DelayedDeref
      )
  /*++
  
  Routine Description:
  
      Add the key body attached to the post block to the delayed deref list.
      Cleans the post block KeyBody member, so it will not be dereferenced 
      when the post block is freed.
  
  Arguments:
  
      PostBlock - pointer to structure that describes the post requests.
  
      DelayedDeref - the delayed deref list
  
  Return Value:
  
      NONE.
  
  --*/
  
  {
      CM_PAGED_CODE();
  
      // common sense
      ASSERT( PostBlock != NULL );
  
      if( PostBlock->PostKeyBody ) {
          //
          // If the post block has a keybody attached, add it to delayed deref list and 
          // clear the post block member. The key body will be deref'd prior after 
          // postblock lock is released.
          //
      
          // extra validation
          ASSERT(PostBlock->PostKeyBody->KeyBody != NULL);
          ASSERT(DelayedDeref);
  
          // add it to the end of the list
          InsertTailList(
              DelayedDeref,
              &(PostBlock->PostKeyBody->KeyBodyList)
              );
      
          // make sure we don't deref it in CmpFreePostBlock
          PostBlock->PostKeyBody = NULL;
      }
  
      return;
  }
  
  VOID
  CmpDelayedDerefKeys(
                      PLIST_ENTRY DelayedDeref
                      )
  /*++
  
  Routine Description:
  
      Walk through the entire list, dereference each keybody and free storage for the 
      CM_POST_KEY_BODY allocated for this purpose.
  
  Arguments:
  
      DelayedDeref - the delayed deref list
  
  Return Value:
  
      NONE.
  
  --*/
  {
      PCM_POST_KEY_BODY   PostKeyBody;
  
      CM_PAGED_CODE();
  
      // common sense
      ASSERT( DelayedDeref != NULL );
  
      while(IsListEmpty(DelayedDeref) == FALSE) {
          //
          // Remove from the delayed deref list and deref the coresponding keybody
          // free the storage associated with CM_POST_KEY_BODY
          //
          PostKeyBody = (PCM_POST_KEY_BODY)RemoveHeadList(DelayedDeref);
          PostKeyBody = CONTAINING_RECORD(PostKeyBody,
                                        CM_POST_KEY_BODY,
                                        KeyBodyList);
  
          // extra validation
          ASSERT(PostKeyBody->KeyBody != NULL);
          // this should be a valid key body
          ASSERT(PostKeyBody->KeyBody->Type == KEY_BODY_TYPE);
          
          // at last ..... dereference the key object
          ObDereferenceObjectDeferDelete(PostKeyBody->KeyBody);
  
          // Free the storage for the CM_POST_KEY_BODY object (allocated by CmpAllocatePostBlock)
          ExFreePool(PostKeyBody);
      }
  }
  