/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    dmnotify.c

Abstract:

    Contains notification support for the Configuration Database Manager

    Each call to DmNotifyChangeKey adds a leaf to the notification tree. This
    tree is expected to be sparse, so each node is implemented as a linked list of
    subnodes and a linked list of leaves.

    When a registry modification occurs, the tree is traversed from the root
    to the leaf representing the key. Any leaves along the path are candidates
    for reporting a notification event.

Author:

    John Vert (jvert) 9/18/1996

Revision History:

--*/
#include "dmp.h"

typedef struct _DM_NOTIFY_BRANCH {
    LIST_ENTRY SiblingList;             // Links onto parent's ChildList.
    LIST_ENTRY ChildList;               // Links onto child's SiblingList.
    LIST_ENTRY LeafList;                // Links
    struct _DM_NOTIFY_BRANCH *Parent;   // Parent
    USHORT NameLength;
    WCHAR KeyName[0];                   // Name component (a single keyname, not a path)
} DM_NOTIFY_BRANCH, *PDM_NOTIFY_BRANCH;

typedef struct _DM_NOTIFY_LEAF {
    LIST_ENTRY SiblingList;             // Links onto parent branch's ChildList
    LIST_ENTRY KeyList;                 // Links onto DMKEY.NotifyList
    LIST_ENTRY RundownList;             // Passed into DmNotifyChangeKey, used for rundown
    HDMKEY     hKey;
    DWORD      CompletionFilter;
    DM_NOTIFY_CALLBACK NotifyCallback;
    DWORD_PTR  Context1;
    DWORD_PTR  Context2;
    PDM_NOTIFY_BRANCH Parent;
    BOOL       WatchTree;
} DM_NOTIFY_LEAF, *PDM_NOTIFY_LEAF;

CRITICAL_SECTION NotifyLock;
PDM_NOTIFY_BRANCH NotifyRoot=NULL;

//
// Local function prototypes
//
VOID
DmpPruneBranch(
    IN PDM_NOTIFY_BRANCH Branch
    );

PDM_NOTIFY_BRANCH
DmpFindKeyInBranch(
    IN PDM_NOTIFY_BRANCH RootBranch,
    IN OUT LPCWSTR *RelativeName,
    OUT WORD *pNameLength
    );

DWORD
DmpAddNotifyLeaf(
    IN PDM_NOTIFY_BRANCH RootBranch,
    IN PDM_NOTIFY_LEAF NewLeaf,
    IN LPCWSTR RelativeName
    );

VOID
DmpReportNotifyWorker(
    IN PDM_NOTIFY_BRANCH RootBranch,
    IN LPCWSTR RelativeName,
    IN LPCWSTR FullName,
    IN DWORD Filter
    );


BOOL
DmpInitNotify(
    VOID
    )
/*++

Routine Description:

    Initializes the notification package for the DM.

Arguments:

    None.

Return Value:

    TRUE if successful

    FALSE otherwise

--*/

{
    InitializeCriticalSection(&NotifyLock);

    return(TRUE);
}


DWORD
DmNotifyChangeKey(
    IN HDMKEY hKey,
    IN DWORD CompletionFilter,
    IN BOOL WatchTree,
    IN OPTIONAL PLIST_ENTRY ListHead,
    IN DM_NOTIFY_CALLBACK NotifyCallback,
    IN DWORD_PTR Context1,
    IN DWORD_PTR Context2
    )
/*++

Routine Description:

    Registers a notification for a specific registry key. When the
    notification event occurs, ApiReportRegistryNotify will be called.

Arguments:

    hKey - Supplies the registry key handle on which the notification
           should be posted.

    CompletionFilter - Supplies the registry events which should trigger
           the notification.

    WatchTree - Supplies whether or not changes to the children of the specified
           key should trigger the notification.

    ListHead - If present, supplies the listhead that the new notification should be
            queued to. This listhead should be passed to DmRundownList.

    NotifyCallback - Supplies the notification routine that should be called
            when the notification occurs.

    Context1 - Supplies the first DWORD of context to be passed to ApiReportRegistryNotify

    Context2 - Supplies the second DWORD of context to be passed to ApiReportRegistryNotify

Return Value:

    ERROR_SUCCESS if successful.

    Win32 error otherwise.

--*/

{
    PDMKEY Key;
    PDM_NOTIFY_LEAF Leaf;
    DWORD Status;

    Key = (PDMKEY)hKey;

    Leaf = LocalAlloc(LMEM_FIXED, sizeof(DM_NOTIFY_LEAF));
    if (Leaf == NULL) {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }
    Leaf->hKey = hKey;
    Leaf->CompletionFilter = CompletionFilter;
    Leaf->WatchTree = WatchTree;
    Leaf->NotifyCallback = NotifyCallback;
    Leaf->Context1 = Context1;
    Leaf->Context2 = Context2;

    EnterCriticalSection(&NotifyLock);

    if (NotifyRoot == NULL) {
        //
        // Create notify root here.
        //
        NotifyRoot = LocalAlloc(LMEM_FIXED, sizeof(DM_NOTIFY_BRANCH));
        if (NotifyRoot == NULL) {
            LeaveCriticalSection(&NotifyLock);
            return(ERROR_NOT_ENOUGH_MEMORY);
        }
        InitializeListHead(&NotifyRoot->SiblingList);
        InitializeListHead(&NotifyRoot->ChildList);
        InitializeListHead(&NotifyRoot->LeafList);
        NotifyRoot->Parent = NULL;
    }

    Status = DmpAddNotifyLeaf(NotifyRoot, Leaf, Key->Name);
    if (Status == ERROR_SUCCESS) {
        InsertHeadList(&Key->NotifyList, &Leaf->KeyList);
        if (ARGUMENT_PRESENT(ListHead)) {
            InsertHeadList(ListHead, &Leaf->RundownList);
        } else {
            Leaf->RundownList.Flink = NULL;
            Leaf->RundownList.Blink = NULL;
        }
    } else {
        LocalFree(Leaf);
    }

    LeaveCriticalSection(&NotifyLock);

    return(Status);
}


VOID
DmRundownList(
    IN PLIST_ENTRY ListHead
    )
/*++

Routine Description:

    Runs down a list of leaves. Used by the API when the notify port
    is closed.

Arguments:

    ListHead - Supplies the head of the rundown list. This is the
        same listhead passed to DmNotifyChangeKey

Return Value:

    None.

--*/

{
    PLIST_ENTRY ListEntry;
    PDM_NOTIFY_LEAF Leaf;

    //
    // Remove all outstanding DM_NOTIFY_LEAF structures from this list.
    //
    EnterCriticalSection(&NotifyLock);
    while (!IsListEmpty(ListHead)) {
        ListEntry = RemoveHeadList(ListHead);
        Leaf = CONTAINING_RECORD(ListEntry,
                                 DM_NOTIFY_LEAF,
                                 RundownList);
        RemoveEntryList(&Leaf->SiblingList);
        RemoveEntryList(&Leaf->KeyList);

        //
        // Attempt to prune this branch.
        //
        DmpPruneBranch(Leaf->Parent);

        LocalFree(Leaf);
    }

    LeaveCriticalSection(&NotifyLock);
}


VOID
DmpRundownNotify(
    IN PDMKEY Key
    )
/*++

Routine Description:

    Cleans up any outstanding notifications for a key when the
    key is being closed.

Arguments:

    Key - Supplies the key

Return Value:

    None.

--*/

{
    PLIST_ENTRY ListEntry;
    PDM_NOTIFY_LEAF Leaf;

    //
    // Remove all outstanding DM_NOTIFY_LEAF structures from this key.
    //
    EnterCriticalSection(&NotifyLock);
    while (!IsListEmpty(&Key->NotifyList)) {
        ListEntry = RemoveHeadList(&Key->NotifyList);
        Leaf = CONTAINING_RECORD(ListEntry,
                                 DM_NOTIFY_LEAF,
                                 KeyList);
        RemoveEntryList(&Leaf->SiblingList);
        if (Leaf->RundownList.Flink != NULL) {
            RemoveEntryList(&Leaf->RundownList);
        }

        //
        // Attempt to prune this branch.
        //
        DmpPruneBranch(Leaf->Parent);

        LocalFree(Leaf);
    }

    LeaveCriticalSection(&NotifyLock);

}


VOID
DmpPruneBranch(
    IN PDM_NOTIFY_BRANCH Branch
    )
/*++

Routine Description:

    Checks to see if a branch is empty and should be pruned (freed).
    If the branch is empty, this routine will recursively call itself
    on the parent until a non-empty branch is found.

Arguments:

    Branch - Supplies the branch to be pruned.

Return Value:

    None.

--*/

{
    if ((IsListEmpty(&Branch->ChildList)) &&
        (IsListEmpty(&Branch->LeafList))) {

        //
        // No need to keep this branch around any more. Remove
        // it from its parent, then check to see if the parent
        // should be pruned.
        //
        if (Branch->Parent == NULL) {
            //
            // This is the root, go ahead and free it up too.
            //
            CL_ASSERT(NotifyRoot == Branch);
            NotifyRoot = NULL;

        } else {
            RemoveEntryList(&Branch->SiblingList);
            DmpPruneBranch(Branch->Parent);
        }
        LocalFree(Branch);
    }
}


DWORD
DmpAddNotifyLeaf(
    IN PDM_NOTIFY_BRANCH RootBranch,
    IN PDM_NOTIFY_LEAF NewLeaf,
    IN LPCWSTR RelativeName
    )
/*++

Routine Description:

    Adds a leaf to the notification key.

    If the RelativeName is empty, a leaf is created in RootBranch.

    If the RelativeName is not empty, look up its first component
    in RootBranch. If it's not there, create it. Then call ourselves
    recursively after stripping off the first component of RelativeName

Arguments:

    RootBranch - Supplies the root where the leaf is to be added

    NewLeaf - Supplies the new leaf structure

    RelativeName - Supplies the relative name.

Return Value:

    ERROR_SUCCESS if successful.

    Win32 error code otherwise.

--*/

{
    PLIST_ENTRY ListEntry;
    PDM_NOTIFY_BRANCH Branch;
    USHORT NameLength;
    LPCWSTR NextName;

    if (RelativeName[0] == '\0') {
        InsertHeadList(&RootBranch->LeafList, &NewLeaf->SiblingList);
        NewLeaf->Parent = RootBranch;
        return(ERROR_SUCCESS);
    }

    NextName = RelativeName;
    Branch = DmpFindKeyInBranch(RootBranch, &NextName, &NameLength);
    if (Branch == NULL) {
        //
        // No branch existed with this name. Create a new branch.
        //
        Branch = LocalAlloc(LMEM_FIXED, sizeof(DM_NOTIFY_BRANCH) + NameLength*sizeof(WCHAR));
        if (Branch == NULL) {
            return(ERROR_NOT_ENOUGH_MEMORY);
        }
        InitializeListHead(&Branch->ChildList);
        InitializeListHead(&Branch->LeafList);
        Branch->Parent = RootBranch;
        Branch->NameLength = NameLength;
        CopyMemory(Branch->KeyName, RelativeName, NameLength*sizeof(WCHAR));
        InsertHeadList(&RootBranch->ChildList, &Branch->SiblingList);
    }

    //
    // Call ourselves recursively on the new branch.
    //
    return(DmpAddNotifyLeaf(Branch, NewLeaf, NextName));
}


VOID
DmpReportNotify(
    IN LPCWSTR KeyName,
    IN DWORD Filter
    )
/*++

Routine Description:

    Interface to the rest of DM to report a notification event on
    a particular key.

Arguments:

    Key - Supplies the key that was modified.

    Filter - Supplies the modification type.

Return Value:

    None.

--*/

{
    if (NotifyRoot == NULL) {
        return;
    }

    EnterCriticalSection(&NotifyLock);

    if (NotifyRoot != NULL) {
        DmpReportNotifyWorker(NotifyRoot,
                              KeyName,
                              KeyName,
                              Filter);
    }

    LeaveCriticalSection(&NotifyLock);

}


VOID
DmpReportNotifyWorker(
    IN PDM_NOTIFY_BRANCH RootBranch,
    IN LPCWSTR RelativeName,
    IN LPCWSTR FullName,
    IN DWORD Filter
    )
/*++

Routine Description:

    Recursive worker routine that drills down through the notification
    tree until it reaches the supplied name. Notifications are issued
    for any leaves along the path that match the event.

Arguments:

    RootBranch - Supplies the branch of the tree to start with.

    RelativeName - Supplies the name of the changed key, relative to Branch.

    FullName - Supplies the full name of the changed key.

    Filter - Supplies the type of event.

Return Value:

    None.

--*/

{
    PLIST_ENTRY ListEntry;
    PDM_NOTIFY_LEAF Leaf;
    PDM_NOTIFY_BRANCH Branch;
    LPCWSTR NextName;
    WORD Dummy;

    //
    // First, issue notifies for any leaves at this node
    //
    ListEntry = RootBranch->LeafList.Flink;
    while (ListEntry != &RootBranch->LeafList) {
        Leaf = CONTAINING_RECORD(ListEntry,
                                 DM_NOTIFY_LEAF,
                                 SiblingList);
        if (Leaf->CompletionFilter & Filter) {
            if ( Leaf->WatchTree ||
                (RelativeName[0] == '\0')) {

                (Leaf->NotifyCallback)(Leaf->Context1,
                                       Leaf->Context2,
                                       Filter,
                                       RelativeName);

            }
        }
        ListEntry = ListEntry->Flink;
    }

    //
    // Now search the child list for a subkey that matches the next component
    // of the key name. If there isn't one, we are done. If there is one,
    // call ourselves recursively on it.
    //
    if (RelativeName[0] == '\0') {
        return;
    }
    NextName = RelativeName;
    Branch = DmpFindKeyInBranch(RootBranch, &NextName, &Dummy);
    if (Branch != NULL) {
        DmpReportNotifyWorker(Branch, NextName, FullName, Filter);
    }

}


PDM_NOTIFY_BRANCH
DmpFindKeyInBranch(
    IN PDM_NOTIFY_BRANCH RootBranch,
    IN OUT LPCWSTR *RelativeName,
    OUT WORD *pNameLength
    )
/*++

Routine Description:

    Finds the next component of a key name in a branch.

Arguments:

    RootBranch - Supplies the branch to search.

    RelativeName - Supplies the relative name of the key.
                   Returns the remaining name

    NameLength - Returns the length of the next component.

Return Value:

    Pointer to the found branch if successful.

    NULL otherwise.

--*/

{
    PDM_NOTIFY_BRANCH Branch;
    USHORT NameLength;
    LPCWSTR NextName;
    PLIST_ENTRY ListEntry;

    //
    // Find the first component of the relative name.
    //
    NextName = wcschr(*RelativeName, '\\');
    if (NextName==NULL) {
        NameLength = (USHORT)lstrlenW(*RelativeName);
        NextName = *RelativeName + NameLength;
    } else {
        NameLength = (USHORT)(NextName - *RelativeName);
        ++NextName;
    }
    *pNameLength = NameLength;

    //
    // Search through the root's children to try and find a match on the
    // first component.
    //
    ListEntry = RootBranch->ChildList.Flink;
    while (ListEntry != &RootBranch->ChildList) {
        Branch = CONTAINING_RECORD(ListEntry,
                                   DM_NOTIFY_BRANCH,
                                   SiblingList);
        if ((NameLength == Branch->NameLength) &&
            (wcsncmp(*RelativeName, Branch->KeyName, NameLength)==0)) {

            //
            // We have matched an existing branch. Return success.
            //
            *RelativeName = NextName;
            return(Branch);
        }
        ListEntry = ListEntry->Flink;
    }
    *RelativeName = NextName;

    return(NULL);

}