/*++ Copyright (c) 1996 Microsoft Corporation Module Name: notify.c Abstract: Public interface for cluster notification API Author: John Vert (jvert) 19-Mar-1996 Revision History: --*/ #include "clusapip.h" // // Define some handy constants // #define FILTER_NODE (CLUSTER_CHANGE_NODE_STATE | \ CLUSTER_CHANGE_NODE_DELETED | \ CLUSTER_CHANGE_NODE_ADDED | \ CLUSTER_CHANGE_NODE_PROPERTY) #define NOT_FILTER_NODE (~(FILTER_NODE |CLUSTER_CHANGE_HANDLE_CLOSE)) #define FILTER_REGISTRY (CLUSTER_CHANGE_REGISTRY_NAME | \ CLUSTER_CHANGE_REGISTRY_ATTRIBUTES | \ CLUSTER_CHANGE_REGISTRY_VALUE | \ CLUSTER_CHANGE_REGISTRY_SUBTREE) #define NOT_FILTER_REGISTRY (~(FILTER_REGISTRY |CLUSTER_CHANGE_HANDLE_CLOSE)) #define FILTER_RESOURCE (CLUSTER_CHANGE_RESOURCE_STATE | \ CLUSTER_CHANGE_RESOURCE_DELETED | \ CLUSTER_CHANGE_RESOURCE_ADDED | \ CLUSTER_CHANGE_RESOURCE_PROPERTY) #define NOT_FILTER_RESOURCE (~(FILTER_RESOURCE | CLUSTER_CHANGE_HANDLE_CLOSE)) #define FILTER_GROUP (CLUSTER_CHANGE_GROUP_STATE | \ CLUSTER_CHANGE_GROUP_DELETED | \ CLUSTER_CHANGE_GROUP_ADDED | \ CLUSTER_CHANGE_GROUP_PROPERTY) #define NOT_FILTER_GROUP (~(FILTER_GROUP | CLUSTER_CHANGE_HANDLE_CLOSE)) #define FILTER_NETWORK (CLUSTER_CHANGE_NETWORK_STATE | \ CLUSTER_CHANGE_NETWORK_DELETED | \ CLUSTER_CHANGE_NETWORK_ADDED | \ CLUSTER_CHANGE_NETWORK_PROPERTY) #define NOT_FILTER_NETWORK (~(FILTER_NETWORK | CLUSTER_CHANGE_HANDLE_CLOSE)) #define FILTER_NETINTERFACE (CLUSTER_CHANGE_NETINTERFACE_STATE | \ CLUSTER_CHANGE_NETINTERFACE_DELETED | \ CLUSTER_CHANGE_NETINTERFACE_ADDED | \ CLUSTER_CHANGE_NETINTERFACE_PROPERTY) #define NOT_FILTER_NETINTERFACE (~(FILTER_NETINTERFACE | CLUSTER_CHANGE_HANDLE_CLOSE)) #define FILTER_CLUSTER (CLUSTER_CHANGE_CLUSTER_STATE | \ CLUSTER_CHANGE_CLUSTER_RECONNECT) #define NOT_FILTER_CLUSTER (~(FILTER_CLUSTER | CLUSTER_CHANGE_HANDLE_CLOSE)) // // Define prototypes for functions local to this module // VOID DestroyNotify( IN PCNOTIFY Notify ); VOID DestroySession( IN PCNOTIFY_SESSION Session ); PCNOTIFY_SESSION CreateNotifySession( IN PCNOTIFY Notify, IN PCLUSTER Cluster ); DWORD AddEventToSession( IN PCNOTIFY_SESSION Session, IN PVOID Object, IN DWORD dwFilter, IN DWORD_PTR dwNotifyKey ); DWORD NotifyThread( IN LPVOID lpThreadParameter ); DWORD GetClusterNotifyCallback( IN PLIST_ENTRY ListEntry, IN OUT PVOID Context ); HCHANGE WINAPI CreateClusterNotifyPort( IN OPTIONAL HCHANGE hChange, IN OPTIONAL HCLUSTER hCluster, IN DWORD dwFilter, IN DWORD_PTR dwNotifyKey ) /*++ Routine Description: Creates a cluster notification port to be used for notification of cluster state changes. Arguments: hChange - Optionally supplies a handle to an existing cluster notification port. If present, the specified notification events will be added to the existing port. hCluster - Optionally supplies a handle to the cluster. If not present, an empty notification port will be created. CreateClusterNotifyPort and RegisterClusterNotify may be used later to add notification events to the notification port. dwFilter - Supplies the events that will be delivered to the notification port. Any events of the specified type will be delivered to the notification port. Currently defined event types are: CLUSTER_CHANGE_NODE_STATE CLUSTER_CHANGE_NODE_DELETED CLUSTER_CHANGE_NODE_ADDED CLUSTER_CHANGE_RESOURCE_STATE CLUSTER_CHANGE_RESOURCE_DELETED CLUSTER_CHANGE_RESOURCE_ADDED CLUSTER_CHANGE_GROUP_STATE CLUSTER_CHANGE_GROUP_DELETED CLUSTER_CHANGE_GROUP_ADDED CLUSTER_CHANGE_RESOURCE_TYPE_DELETED CLUSTER_CHANGE_RESOURCE_TYPE_ADDED CLUSTER_CHANGE_QUORUM_STATE dwNotifyKey - Supplies the notification key to be returned as part of the notification event. Return Value: If the function is successful, the return value is a handle of the change notification object. If the function fails, the return value is NULL. To get extended error information, call GetLastError. --*/ { PCNOTIFY Notify; PCLUSTER Cluster; DWORD Status; PCNOTIFY_SESSION Session; if (hChange == INVALID_HANDLE_VALUE) { // // This is a newly created notification session // Notify = LocalAlloc(LMEM_FIXED, sizeof(CNOTIFY)); if (Notify == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return(NULL); } InitializeListHead(&Notify->SessionList); InitializeListHead(&Notify->OrphanedEventList); InitializeCriticalSection(&Notify->Lock); ClRtlInitializeQueue(&Notify->Queue); #ifdef _WIN64 ClRtlInitializeHash(&Notify->NotifyKeyHash); #else ZeroMemory(&Notify->NotifyKeyHash,sizeof(CL_HASH)); #endif if (hCluster == INVALID_HANDLE_VALUE) { // // Caller has asked for an empty notification port. // return((HCHANGE)Notify); } } else { // // This is an existing notification port that the specified // cluster should be added to. // Notify = (PCNOTIFY)hChange; if ((hCluster == INVALID_HANDLE_VALUE) || (hCluster == NULL)) { SetLastError(ERROR_INVALID_PARAMETER); return(NULL); } } Cluster = (PCLUSTER)hCluster; // // Chittur Subbaraman (chitturs) - 4/11/2000 // // Make sure the cluster lock is acquired before the notify lock. // If this order is violated, it could be a potential source of // hard-to-track deadlocks. // EnterCriticalSection(&Cluster->Lock); EnterCriticalSection(&Notify->Lock); Session = CreateNotifySession(Notify, Cluster); if (Session == NULL) { Status = GetLastError(); LeaveCriticalSection(&Notify->Lock); LeaveCriticalSection(&Cluster->Lock); if (hChange == INVALID_HANDLE_VALUE) { DestroyNotify(Notify); } SetLastError(Status); return(NULL); } Status = AddEventToSession(Session, NULL, dwFilter, dwNotifyKey); LeaveCriticalSection(&Notify->Lock); LeaveCriticalSection(&Cluster->Lock); if (Status != ERROR_SUCCESS) { if (hChange == INVALID_HANDLE_VALUE) { DestroyNotify(Notify); } SetLastError(Status); return(NULL); } TIME_PRINT(("CreateClusterNotifyPort: Returning Notify=0x%08lx\n", Notify)); return((HCHANGE)Notify); } PCNOTIFY_SESSION CreateNotifySession( IN PCNOTIFY Notify, IN PCLUSTER Cluster ) /*++ Routine Description: This routine finds a notification session to the specified cluster. If a session already exists, it is found and used. If a session does not exist, a new one is created. The Notify lock must be held. Arguments: Notify - Supplies the notification port. Cluster - Supplies the cluster that the session should be opened to. Return Value: A pointer to the notification session. NULL on error, GetLastError() will return the specific error code. --*/ { PLIST_ENTRY ListEntry; PCNOTIFY_SESSION Session; error_status_t Status = ERROR_SUCCESS; // // First, try to find an existing session. // ListEntry = Notify->SessionList.Flink; while (ListEntry != &Notify->SessionList) { Session = CONTAINING_RECORD(ListEntry, CNOTIFY_SESSION, ListEntry); if (Session->Cluster == Cluster) { TIME_PRINT(("CreateNotifySession: found a matching session\n")); // // Found a match, return it directly. // return(Session); } ListEntry = ListEntry->Flink; } // // There is no existing session. Go ahead and create a new one. // Session = LocalAlloc(LMEM_FIXED, sizeof(CNOTIFY_SESSION)); if (Session == NULL) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return(NULL); } TIME_PRINT(("CreateNotifySession: Calling ApiCreateNotify\n")); WRAP_NULL(Session->hNotify, (ApiCreateNotify(Cluster->RpcBinding, &Status)), &Status, Cluster); if ((Session->hNotify == NULL) || (Status != ERROR_SUCCESS)) { LocalFree(Session); SetLastError(Status); return(NULL); } InitializeListHead(&Session->EventList); Session->Cluster = Cluster; Session->ParentNotify = Notify; Session->Destroyed = FALSE; // // Spin up the notification thread for this session. // Session->NotifyThread = CreateThread(NULL, 0, NotifyThread, Session, 0, NULL); if (Session->NotifyThread == NULL) { Status = GetLastError(); ApiCloseNotify(&Session->hNotify); LocalFree(Session); SetLastError(Status); return(NULL); } InsertHeadList(&Notify->SessionList, &Session->ListEntry); EnterCriticalSection(&Cluster->Lock); InsertHeadList(&Cluster->SessionList, &Session->ClusterList); LeaveCriticalSection(&Cluster->Lock); TIME_PRINT(("CreateNotifySession: Session=0x%08lx hNotifyRpc=0x%08lx Thread=0x%08lx\n", Session, Session->hNotify, NotifyThread)); return(Session); } DWORD NotifyThread( IN LPVOID lpThreadParameter ) /*++ Routine Description: Notification thread that gets notification messages from the cluster and reposts them to the client-side notify queue. Arguments: lpThreadParameter - Supplies the CNOTIFY_SESSION structure to be monitored Return Value: None. --*/ { PCNOTIFY_SESSION Session = (PCNOTIFY_SESSION)lpThreadParameter; PCLUSTER Cluster = Session->Cluster; PLIST_ENTRY ListEntry; PCNOTIFY_EVENT Event; DWORD Status = ERROR_INVALID_HANDLE_STATE; error_status_t rpc_error; PCNOTIFY_PACKET Packet; LPWSTR Name; do { if (Session->Destroyed) { TIME_PRINT(("NotifyThread: Session 0x%08lx destroyed\n", Session)); break; } Packet = LocalAlloc(LMEM_FIXED, sizeof(CNOTIFY_PACKET)); if (Packet == NULL) { Status = ERROR_NOT_ENOUGH_MEMORY; break; } Packet->Status = ERROR_SUCCESS; Packet->Name = NULL; TIME_PRINT(("NotifyThread: Calling ApiGetNotify, hNotify=0x%08lx\n", Session->hNotify)); WRAP_CHECK(Status, (ApiGetNotify(Session->hNotify, INFINITE, &Packet->KeyId, &Packet->Filter, &Packet->StateSequence, &Packet->Name)), Session->Cluster, !Session->Destroyed); if (Status != ERROR_SUCCESS) { TIME_PRINT(("NotifyThread : ApiGetNotify on hNotify=0x%08lx returns %u\n", Session->hNotify, Status)); //if the error is due to a reconnect, hide it and map it to success if ((Status == ERROR_NO_MORE_ITEMS) && (Session->hNotify != NULL)) { //set the status to sucess again - this might happen on a //reconnect and then we do want to continue //so we retry apigetnotify again Status = ERROR_SUCCESS; LocalFree(Packet); TIME_PRINT(("NotifyThread : Reconnect map error to success\n")); } else { //when can we be sure that the cluster is dead? //If session is null(reconnect failed) OR //If the cluster is marked dead(reconnect failed after session was established) OR //If the cluster is dead, and wrap returns RPC_S_SERVER_UNAVAILABLE //if so, we can terminate this thread because the thread //maps to a cluster //what do we document, if this returns error, call closeclusternotifyport if ((Session->hNotify == NULL) || (Session->Cluster->Flags & CLUS_DEAD) || (Status == RPC_S_SERVER_UNAVAILABLE)) { //SS: it is not clear why we post this event //multiple times??? Chittur, any ideas???? //Does this mean that if you register for the //same filter twice, you get the event twice? // We should probably hold the cluster lock here EnterCriticalSection(&Cluster->Lock); //That seems bizarre. // // Something horrible has happened, probably the cluster has crashed. // // Run down the notify list for this cluster and post a packet for // each registered notify event for CLUSTER_CHANGE_CLUSTER_STATE // Name = Cluster->ClusterName; ListEntry = Cluster->NotifyList.Flink; while (ListEntry != &Cluster->NotifyList) { Event = CONTAINING_RECORD(ListEntry, CNOTIFY_EVENT, ObjectList); if (Event->dwFilter & CLUSTER_CHANGE_CLUSTER_STATE) { if (Packet == NULL) { Packet = LocalAlloc(LMEM_FIXED, sizeof(CNOTIFY_PACKET)); if (Packet == NULL) { LeaveCriticalSection(&Cluster->Lock); return(ERROR_NOT_ENOUGH_MEMORY); } } //SS: Dont know what the Status was meant for //It looks like it is not being used Packet->Status = ERROR_SUCCESS; Packet->Filter = CLUSTER_CHANGE_CLUSTER_STATE; Packet->KeyId = Event->EventId; Packet->Name = MIDL_user_allocate((lstrlenW(Name)+1)*sizeof(WCHAR)); if (Packet->Name != NULL) { lstrcpyW(Packet->Name, Name); } TIME_PRINT(("NotifyThread - posting CLUSTER_CHANGE_CLUSTER_STATE to notify queue\n")); ClRtlInsertTailQueue(&Session->ParentNotify->Queue, &Packet->ListEntry); Packet = NULL; } ListEntry = ListEntry->Flink; } LeaveCriticalSection(&Cluster->Lock); //cluster is dead, map the error to success Status = ERROR_SUCCESS; //break out of the loop to terminate this thread TIME_PRINT(("NotifyThread : Cluster is dead, break to exit notify thread\n")); LocalFree(Packet); break; } else { //it is some other error, the user must //call closeclusternotify port to clean up //this thread //free the packet LocalFree(Packet); } } } else { // // Post this onto the notification queue // ClRtlInsertTailQueue(&Session->ParentNotify->Queue, &Packet->ListEntry); } } while ( Status == ERROR_SUCCESS ); return(Status); } DWORD AddEventToSession( IN PCNOTIFY_SESSION Session, IN PVOID Object, IN DWORD dwFilter, IN DWORD_PTR dwNotifyKey ) /*++ Routine Description: Adds a specific event to a cluster notification session Arguments: Notify - Supplies the notify object Object - Supplies the specific object, NULL if it is the cluster. dwFilter - Supplies the type of events dwNotifyKey - Supplies the notification key to be returned. Return Value: ERROR_SUCCESS if successful Win32 error code otherwise. --*/ { PCNOTIFY_EVENT NotifyEvent; PCLUSTER Cluster; PLIST_ENTRY NotifyList; DWORD Status; Cluster = Session->Cluster; NotifyEvent = LocalAlloc(LMEM_FIXED, sizeof(CNOTIFY_EVENT)); if (NotifyEvent == NULL) { return(ERROR_NOT_ENOUGH_MEMORY); } NotifyEvent->Session = Session; NotifyEvent->dwFilter = dwFilter; NotifyEvent->dwNotifyKey = dwNotifyKey; NotifyEvent->Object = Object; #ifdef _WIN64 NotifyEvent->EventId = 0; Status = ClRtlInsertTailHash(&Session->ParentNotify->NotifyKeyHash, NotifyEvent, &NotifyEvent->EventId); if (ERROR_SUCCESS != Status) { LocalFree(NotifyEvent); return(Status); } #else NotifyEvent->EventId=(DWORD)NotifyEvent; #endif WRAP(Status, (RegisterNotifyEvent(Session, NotifyEvent, &NotifyList)), Cluster); if (Status != ERROR_SUCCESS) { #ifdef _WIN64 ClRtlRemoveEntryHash(&Session->ParentNotify->NotifyKeyHash, NotifyEvent->EventId); #endif LocalFree(NotifyEvent); return(Status); } // // Add this notification event to the appropriate lists so it can be // recreated when the cluster node fails. // EnterCriticalSection(&Cluster->Lock); EnterCriticalSection(&Session->ParentNotify->Lock); InsertHeadList(&Session->EventList, &NotifyEvent->ListEntry); InsertHeadList(NotifyList, &NotifyEvent->ObjectList); LeaveCriticalSection(&Session->ParentNotify->Lock); LeaveCriticalSection(&Cluster->Lock); return(ERROR_SUCCESS); } DWORD RegisterNotifyEvent( IN PCNOTIFY_SESSION Session, IN PCNOTIFY_EVENT Event, OUT OPTIONAL PLIST_ENTRY *pNotifyList ) /*++ Routine Description: Common routine for registering a notification event on a cluster session Arguments: Session - Supplies the notification session the event should be added to. Event - Supplies the event to be added to the session. NotifyList - if present, returns the list that the notification event should be added to. Return Value: ERROR_SUCCESS if successful Win32 error otherwise. --*/ { DWORD Status; if (Event->Object == NULL) { TIME_PRINT(("RegisterNotifyEvent : Calling ApiAddNotifyCluster\n")); Status = ApiAddNotifyCluster(Session->hNotify, Session->Cluster->hCluster, Event->dwFilter, Event->EventId); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &Session->Cluster->NotifyList; } } else if (Event->dwFilter & FILTER_NODE) { Status = ApiAddNotifyNode(Session->hNotify, ((PCNODE)(Event->Object))->hNode, Event->dwFilter, Event->EventId, &Event->StateSequence); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &((PCNODE)(Event->Object))->NotifyList; } } else if (Event->dwFilter & FILTER_RESOURCE) { Status = ApiAddNotifyResource(Session->hNotify, ((PCRESOURCE)(Event->Object))->hResource, Event->dwFilter, Event->EventId, &Event->StateSequence); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &((PCRESOURCE)(Event->Object))->NotifyList; } } else if (Event->dwFilter & FILTER_GROUP) { Status = ApiAddNotifyGroup(Session->hNotify, ((PCGROUP)(Event->Object))->hGroup, Event->dwFilter, Event->EventId, &Event->StateSequence); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &((PCGROUP)(Event->Object))->NotifyList; } } else if (Event->dwFilter & FILTER_NETWORK) { Status = ApiAddNotifyNetwork(Session->hNotify, ((PCNETWORK)(Event->Object))->hNetwork, Event->dwFilter, Event->EventId, &Event->StateSequence); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &((PCNETWORK)(Event->Object))->NotifyList; } } else if (Event->dwFilter & FILTER_NETINTERFACE) { Status = ApiAddNotifyNetInterface(Session->hNotify, ((PCNETINTERFACE)(Event->Object))->hNetInterface, Event->dwFilter, Event->EventId, &Event->StateSequence); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &((PCNETINTERFACE)(Event->Object))->NotifyList; } } else if (Event->dwFilter & FILTER_REGISTRY) { Status = ApiAddNotifyKey(Session->hNotify, ((PCKEY)(Event->Object))->RemoteKey, Event->EventId, Event->dwFilter & ~CLUSTER_CHANGE_REGISTRY_SUBTREE, (Event->dwFilter & CLUSTER_CHANGE_REGISTRY_SUBTREE) ? TRUE : FALSE); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &((PCKEY)(Event->Object))->NotifyList; } } else if (Event->dwFilter & FILTER_CLUSTER) { Status = ERROR_SUCCESS; if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &Session->Cluster->NotifyList; } } else { return(ERROR_INVALID_PARAMETER); } TIME_PRINT(("RegisterNotifyEvent :returned 0x%08lx\n", Status)); return(Status); } DWORD ReRegisterNotifyEvent( IN PCNOTIFY_SESSION Session, IN PCNOTIFY_EVENT Event, OUT OPTIONAL PLIST_ENTRY *pNotifyList ) /*++ Routine Description: Common routine for re-registering a notification event on a cluster session. The only difference between this and RegisterNotifyEvent is that this passes the SessionState DWORD to the server, which will cause an immediate notification trigger if it does not match. Arguments: Session - Supplies the notification session the event should be added to. Event - Supplies the event to be added to the session. NotifyList - if present, returns the list that the notification event should be added to. Return Value: ERROR_SUCCESS if successful Win32 error otherwise. --*/ { DWORD Status; if (Event->Object == NULL) { Status = ApiAddNotifyCluster(Session->hNotify, Session->Cluster->hCluster, Event->dwFilter, Event->EventId); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &Session->Cluster->NotifyList; } } else if (Event->dwFilter & FILTER_NODE) { Status = ApiReAddNotifyNode(Session->hNotify, ((PCNODE)(Event->Object))->hNode, Event->dwFilter, Event->EventId, Event->StateSequence); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &((PCNODE)(Event->Object))->NotifyList; } } else if (Event->dwFilter & FILTER_RESOURCE) { Status = ApiReAddNotifyResource(Session->hNotify, ((PCRESOURCE)(Event->Object))->hResource, Event->dwFilter, Event->EventId, Event->StateSequence); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &((PCRESOURCE)(Event->Object))->NotifyList; } } else if (Event->dwFilter & FILTER_GROUP) { Status = ApiReAddNotifyGroup(Session->hNotify, ((PCGROUP)(Event->Object))->hGroup, Event->dwFilter, Event->EventId, Event->StateSequence); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &((PCGROUP)(Event->Object))->NotifyList; } } else if (Event->dwFilter & FILTER_NETWORK) { Status = ApiReAddNotifyNetwork(Session->hNotify, ((PCNETWORK)(Event->Object))->hNetwork, Event->dwFilter, Event->EventId, Event->StateSequence); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &((PCNETWORK)(Event->Object))->NotifyList; } } else if (Event->dwFilter & FILTER_NETINTERFACE) { Status = ApiReAddNotifyNetInterface(Session->hNotify, ((PCNETINTERFACE)(Event->Object))->hNetInterface, Event->dwFilter, Event->EventId, Event->StateSequence); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &((PCNETINTERFACE)(Event->Object))->NotifyList; } } else if (Event->dwFilter & FILTER_REGISTRY) { Status = ApiAddNotifyKey(Session->hNotify, ((PCKEY)(Event->Object))->RemoteKey, Event->EventId, Event->dwFilter & ~CLUSTER_CHANGE_REGISTRY_SUBTREE, (Event->dwFilter & CLUSTER_CHANGE_REGISTRY_SUBTREE) ? TRUE : FALSE); if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &((PCKEY)(Event->Object))->NotifyList; } } else if (Event->dwFilter & FILTER_CLUSTER) { Status = ERROR_SUCCESS; if (ARGUMENT_PRESENT(pNotifyList)) { *pNotifyList = &Session->Cluster->NotifyList; } } else { return(ERROR_INVALID_PARAMETER); } return(Status); } VOID DestroyNotify( IN PCNOTIFY Notify ) /*++ Routine Description: Cleans up and frees all allocations and references associated with a notification session. Arguments: Notify - supplies the CNOTIFY structure to be destroyed Return Value: None. --*/ { PCNOTIFY_SESSION Session; PLIST_ENTRY ListEntry; PLIST_ENTRY EventList; PCRESOURCE Resource; PCGROUP Group; PCNODE Node; PCLUSTER Cluster; PCNOTIFY_EVENT Event; LIST_ENTRY QueueEntries; PCNOTIFY_PACKET Packet; // // Rundown each session associated with this notification session // while (!IsListEmpty(&Notify->SessionList)) { ListEntry = RemoveHeadList(&Notify->SessionList); Session = CONTAINING_RECORD(ListEntry, CNOTIFY_SESSION, ListEntry); Cluster = Session->Cluster; EnterCriticalSection(&Cluster->Lock); // // Rundown each event registered on this session. // while (!IsListEmpty(&Session->EventList)) { EventList = RemoveHeadList(&Session->EventList); Event = CONTAINING_RECORD(EventList, CNOTIFY_EVENT, ListEntry); RemoveEntryList(&Event->ObjectList); LocalFree(Event); } DestroySession(Session); LeaveCriticalSection(&Cluster->Lock); } // // Rundown any outstanding notifications remaining on the queue // ClRtlRundownQueue(&Notify->Queue, &QueueEntries); while (!IsListEmpty(&QueueEntries)) { ListEntry = RemoveHeadList(&QueueEntries); Packet = CONTAINING_RECORD(ListEntry, CNOTIFY_PACKET, ListEntry); MIDL_user_free(Packet->Name); LocalFree(Packet); } // // Now that we know there are no outstanding references to the orphaned // events, free up anything on that list. // while (!IsListEmpty(&Notify->OrphanedEventList)) { ListEntry = RemoveHeadList(&Notify->OrphanedEventList); Event = CONTAINING_RECORD(ListEntry, CNOTIFY_EVENT, ListEntry); LocalFree(Event); } DeleteCriticalSection(&Notify->Lock); ClRtlDeleteQueue(&Notify->Queue); #ifdef _WIN64 ClRtlDeleteHash(&Notify->NotifyKeyHash); #endif LocalFree(Notify); } DWORD WINAPI RegisterClusterNotify( IN HCHANGE hChange, IN DWORD dwFilterType, IN HANDLE hObject, IN DWORD_PTR dwNotifyKey ) /*++ Routine Description: Adds a specific notification type to a cluster notification port. This allows an application to register for notification events that affect only a particular cluster object. The currently supported specific cluster objects are nodes, resources, and groups. Arguments: hChange - Supplies the change notification object. dwFilterType - Supplies the type of object that the specific notification events should be delivered for. hObject is a handle to an object of this type. Currently supported specific filters include: CLUSTER_CHANGE_NODE_STATE - hObject is an HNODE CLUSTER_CHANGE_RESOURCE_STATE - hObject is an HRESOURCE CLUSTER_CHANGE_GROUP_STATE - hObject is an HGROUP CLUSTER_CHANGE_REGISTRY_NAME \ CLUSTER_CHANGE_REGISTRY_ATTRIBUTES \ - hObject is an HKEY CLUSTER_CHANGE_REGISTRY_VALUE / CLUSTER_CHANGE_REGISTRY_SUBTREE / hObject - Supplies the handle to the specific object of the type specified by dwFilterType. dwNotifyKey - Supplies the notification key to be returned as part of the notification event. Return Value: ERROR_SUCCESS if successful. Win32 error code otherwise. --*/ { PCNOTIFY Notify; PCLUSTER Cluster; PCNOTIFY_SESSION Session; DWORD dwStatus; if (dwFilterType & FILTER_NODE) { if (dwFilterType & NOT_FILTER_NODE) { return(ERROR_INVALID_PARAMETER); } Cluster = ((PCNODE)hObject)->Cluster; } else if (dwFilterType & FILTER_RESOURCE) { if (dwFilterType & NOT_FILTER_RESOURCE) { return(ERROR_INVALID_PARAMETER); } Cluster = ((PCRESOURCE)hObject)->Cluster; } else if (dwFilterType & FILTER_GROUP) { if (dwFilterType & NOT_FILTER_GROUP) { return(ERROR_INVALID_PARAMETER); } Cluster = ((PCGROUP)hObject)->Cluster; } else if (dwFilterType & FILTER_NETWORK) { if (dwFilterType & NOT_FILTER_NETWORK) { return(ERROR_INVALID_PARAMETER); } Cluster = ((PCNETWORK)hObject)->Cluster; } else if (dwFilterType & FILTER_NETINTERFACE) { if (dwFilterType & NOT_FILTER_NETINTERFACE) { return(ERROR_INVALID_PARAMETER); } Cluster = ((PCNETINTERFACE)hObject)->Cluster; } else if (dwFilterType & FILTER_REGISTRY) { if (dwFilterType & NOT_FILTER_REGISTRY) { return(ERROR_INVALID_PARAMETER); } Cluster = ((PCKEY)hObject)->Cluster; } else if (dwFilterType & FILTER_CLUSTER){ if (dwFilterType & NOT_FILTER_CLUSTER){ return(ERROR_INVALID_PARAMETER); } Cluster = (PCLUSTER)hObject; } else { return(ERROR_INVALID_PARAMETER); } Notify = (PCNOTIFY)hChange; EnterCriticalSection(&Cluster->Lock); EnterCriticalSection(&Notify->Lock); Session = CreateNotifySession(Notify, Cluster); if (Session == NULL) { LeaveCriticalSection(&Notify->Lock); LeaveCriticalSection(&Cluster->Lock); return(GetLastError()); } dwStatus = AddEventToSession(Session, hObject, dwFilterType, dwNotifyKey); LeaveCriticalSection(&Notify->Lock); LeaveCriticalSection(&Cluster->Lock); return( dwStatus ); } DWORD WINAPI GetClusterNotify( IN HCHANGE hChange, OUT DWORD_PTR *lpdwNotifyKey, OUT LPDWORD lpdwFilterType, OUT OPTIONAL LPWSTR lpszName, IN OUT LPDWORD lpcchName, IN DWORD dwMilliseconds ) /*++ Routine Description: Returns the next event from a cluster notification port. Arguments: hChange - Supplies the cluster notification port. lpdwNotifyKey - Returns the notification key for the notification event. This is the key passed to CreateClusterNotifyPort or RegisterClusterNotify. lpdwFilterType - Returns the type of the notification event. lpszName - Optionally returns the name of the object that triggered the notification event. lpcchName - Supplies the length (in characters) of the lpszName buffer. This length includes the space for any trailing NULL. Returns the length (in characters) of the name written into the lpszName buffer. This length does not include the trailing NULL. dwMilliseconds - Supplies an optional timeout value that specifies how long the caller is willing to wait for the cluster notification event. Return Value: ERROR_SUCCESS if successful. If lpszName is NULL we return success and fill in lpcchName with the size. If lpcchName is NULL we return ERROR_MORE_DATA. ERROR_MORE_DATA if the buffer is too small. Win32 error code otherwise. --*/ { PCNOTIFY_PACKET Packet; PLIST_ENTRY ListEntry; PCNOTIFY Notify = (PCNOTIFY)hChange; DWORD Length; DWORD Status; PCNOTIFY_EVENT Event; PVOID BufferArray[2]; BufferArray[0] = lpszName; BufferArray[1] = lpcchName; // // ListEntry will be NULL under the following conditions (as determined by the ret value from // GetClusterNotifyCallback): // // lpszName == NULL, lpcchName != NULL (looking for a buffer size) (ERROR_MORE_DATA) // lpszName != NULL, lpcchName != NULL, and *lpcchName <= Length (ERROR_MORE_DATA) // ListEntry = ClRtlRemoveHeadQueueTimeout(&Notify->Queue, dwMilliseconds, GetClusterNotifyCallback,BufferArray); if (ListEntry == NULL) { // // The queue has been rundown or a timeout has occurred, or the buffer isn't big enough. // Status = GetLastError(); if (lpszName==NULL && lpcchName!=NULL) { // // We returned ERROR_MORE_DATA from GetClusterNotifyCallback to prevent a dequeueing, // but we want to return ERROR_SUCCESS because a buffer wasn't specified (maintains // consistency with the other Cluster APIs) // Status = ERROR_SUCCESS; } return(Status); } Packet = CONTAINING_RECORD(ListEntry, CNOTIFY_PACKET, ListEntry); #ifdef _WIN64 Event = (PCNOTIFY_EVENT)ClRtlGetEntryHash(&Notify->NotifyKeyHash, Packet->KeyId); if (Event == NULL) { // // The entry is missing // MIDL_user_free(Packet->Name); LocalFree(Packet); // // should not happen unless the memory is corrupted // return(ERROR_NOT_FOUND); } #else Event = (PCNOTIFY_EVENT)Packet->KeyId; #endif Event->StateSequence = Packet->StateSequence; *lpdwNotifyKey = Event->dwNotifyKey; *lpdwFilterType = Packet->Filter; if (ARGUMENT_PRESENT(lpszName)) { MylstrcpynW(lpszName, Packet->Name, *lpcchName); Length = lstrlenW(Packet->Name); if (Length < *lpcchName) { *lpcchName = Length; } } MIDL_user_free(Packet->Name); LocalFree(Packet); return(ERROR_SUCCESS); } BOOL WINAPI CloseClusterNotifyPort( IN HCHANGE hChange ) /*++ Routine Description: Closes a handle of a change notification object. Arguments: hChange - Supplies a handle of a cluster change notification object to close. Return Value: If the function is successful, the return value is TRUE. If the function fails, the return value is FALSE. To get extended error information, call GetLastError. Remarks: --*/ { PCNOTIFY Notify = (PCNOTIFY)hChange; DestroyNotify(Notify); return(TRUE); } VOID RundownNotifyEvents( IN PLIST_ENTRY ListHead, IN LPCWSTR lpszName ) /*++ Routine Description: Cleans up any notification events on the specified list. Arguments: ListHead - Supplies the head of the list of notification events. lpszName - Supplies the name that should be used to post the handle close event. Return Value: None. --*/ { PCNOTIFY_EVENT Event; PLIST_ENTRY ListEntry; PCRITICAL_SECTION Lock; PCNOTIFY_PACKET Packet; while (!IsListEmpty(ListHead)) { ListEntry = RemoveHeadList(ListHead); Event = CONTAINING_RECORD(ListEntry, CNOTIFY_EVENT, ObjectList); // // Allocate a notification packet for delivering the handle // close notification. // if (Event->dwFilter & CLUSTER_CHANGE_HANDLE_CLOSE) { Packet = LocalAlloc(LMEM_FIXED, sizeof(CNOTIFY_PACKET)); if (Packet != NULL) { Packet->Status = ERROR_SUCCESS; Packet->KeyId = Event->EventId; Packet->Filter = (DWORD)CLUSTER_CHANGE_HANDLE_CLOSE; Packet->StateSequence = Event->StateSequence; Packet->Name = MIDL_user_allocate((lstrlenW(lpszName)+1)*sizeof(WCHAR)); if (Packet->Name == NULL) { LocalFree(Packet); Packet = NULL; } else { lstrcpyW(Packet->Name, lpszName); ClRtlInsertTailQueue(&Event->Session->ParentNotify->Queue, &Packet->ListEntry); } } } Lock = &Event->Session->ParentNotify->Lock; EnterCriticalSection(Lock); RemoveEntryList(&Event->ListEntry); // // Note that we cannot just free the Event structure since there may be // notification packets that reference this event in either the server-side // or client-side queues. Instead we store it on the orphaned event list. // It will be cleaned up when the session is closed or when a reconnect // occurs. If we had some way to flush out the event queue we could use // that instead. // InsertTailList(&Event->Session->ParentNotify->OrphanedEventList, &Event->ListEntry); if (IsListEmpty(&Event->Session->EventList)) { DestroySession(Event->Session); } LeaveCriticalSection(Lock); } } VOID DestroySession( IN PCNOTIFY_SESSION Session ) /*++ Routine Description: Destroys and cleans up an empty notification session. This means closing the RPC context handle and waiting for the notification thread to terminate itself. The session will be removed from the notification ports list. The session must be empty. N.B. The cluster lock must be held. Arguments: Session - Supplies the session to be destroyed. Return Value: None. --*/ { DWORD dwStatus = ERROR_SUCCESS; // // Chittur Subbaraman (chitturs) - 4/19/2000 // // In order to prevent the NotifyThread from calling ApiGetNotify // during or after the context handle is destroyed, we split // the notification port close into two steps. In the first step, // we merely unblock the ApiGetNotify call and then wait for // the NotifyThread to terminate without freeing the context handle. // In the next step, after making sure that the NotifyThread has // terminated, we free the context handle. This avoids an AV in RPC // code caused by the ApiGetNotify call being made during or soon after // the context handle is freed. // Session->Destroyed = TRUE; TIME_PRINT(("Destroy session: Session 0x%08lx marked as destroyed\n", Session)); // // If the cluster is not dead, try to unblock the ApiGetNotify call. // if ( !( Session->Cluster->Flags & CLUS_DEAD ) && ( Session->hNotify != NULL ) ) { TIME_PRINT(("Destroy session: Call ApiUnblockGetNotifyThread before NotifyThread termination, hNotify = 0x%08lx\n", Session->hNotify)); dwStatus = ApiUnblockGetNotifyCall( Session->hNotify ); } // // If the ApiUnblockGetNotifyThread returned RPC_S_PROCNUM_OUT_OF_RANGE, // it means you are talking to a server that does not support that // API. Revert to the old (buggy) behavior then. // if ( dwStatus == RPC_S_PROCNUM_OUT_OF_RANGE ) { TIME_PRINT(("Destroy session: Call ApiCloseNotify before NotifyThread termination, hNotify = 0x%08lx\n", Session->hNotify)); if ( ApiCloseNotify( &Session->hNotify ) != ERROR_SUCCESS ) { TIME_PRINT(("Destroy session: Call RpcSmDestroyClientContext since ApiCloseNotify failed before terminating NotifyThread, hNotify = 0x%08lx\n", Session->hNotify)); RpcSmDestroyClientContext( &Session->hNotify ); } } RemoveEntryList( &Session->ListEntry ); RemoveEntryList( &Session->ClusterList ); // // Drop the critical section as the notification thread might be // stuck waiting on it. Since the session has been removed from // the cluster list, nobody can get to it anymore. // LeaveCriticalSection( &Session->Cluster->Lock ); WaitForSingleObject( Session->NotifyThread, INFINITE ); CloseHandle( Session->NotifyThread ); // // Reacquire the cluster lock. // EnterCriticalSection( &Session->Cluster->Lock ); // // If the ApiUnblockGetNotifyThread was successfully executed or // it could not be made since the cluster was dead, then perform // the context handle cleanup. Note that cleaning up the context // handle here is safe since we know that the NotifyThread has // terminated at this point and wouldn't use it any more. // if ( dwStatus != RPC_S_PROCNUM_OUT_OF_RANGE ) { if ( Session->Cluster->Flags & CLUS_DEAD ) { TIME_PRINT(("Destroy session: Call RpcSmDestroyClientContext after terminating NotifyThread, hNotify = 0x%08lx\n", Session->hNotify)); if ( Session->hNotify != NULL ) { RpcSmDestroyClientContext( &Session->hNotify ); } } else { TIME_PRINT(("Destroy session: Call ApiCloseNotify after terminating NotifyThread, hNotify = 0x%08lx\n", Session->hNotify)); dwStatus = ApiCloseNotify( &Session->hNotify ); if ( dwStatus != ERROR_SUCCESS ) { TIME_PRINT(("Destroy session: Call RpcSmDestroyClientContext since ApiCloseNotify failed after terminating NotifyThread, hNotify = 0x%08lx\n", Session->hNotify)); RpcSmDestroyClientContext( &Session->hNotify ); } } } LocalFree( Session ); } DWORD GetClusterNotifyCallback( IN PLIST_ENTRY ListEntry, IN OUT PVOID pvContext ) /*++ Routine Description: Check ListEntry to determine whether the buffer is big enough to contain the Name Arguments: ListEntry - Supplies the event to convert to a CNOTIFY_PACKET. Context - A len 2 PVOID array containing the buffer pointer and a DWORD ptr to the buffer length. On output the buffer len ptr contains the number of chars needed. Return Value: ERROR_SUCCESS - The buffer is large enough to put the Name into. ERROR_MORE_DATA - The buffer is too small. --*/ { PCNOTIFY_PACKET Packet; DWORD Length; LPWSTR pBuffer; DWORD* pBufferLength; PVOID *Context = (PVOID*)pvContext; DWORD Status; ASSERT( pvContext != NULL ); pBuffer = (LPWSTR)(Context[0]); pBufferLength = (DWORD*)(Context[1]); // // Check the Name buffer size // Packet = CONTAINING_RECORD( ListEntry, CNOTIFY_PACKET, ListEntry ); // // Nested if's to cover the four combinations of pBufferLength and pBuffer being // NULL and non-NULL values. // if ( pBufferLength == NULL) { if (pBuffer == NULL ) { // // We're not interested in filling a buffer, return ERROR_SUCCESS. This will // cause an event to be dequeued. // Status = ERROR_SUCCESS; } else { // pBuffer != NULL // // AV to maintain pre-Whistler functionality (ugh) // *pBufferLength = 0; Status = ERROR_INVALID_PARAMETER; } } else { // // pBufferLength != NULL; // Length = wcslen( Packet->Name ); if (pBuffer == NULL ) { // // We're only interested in getting a buffer size, return ERROR_MORE_DATA to // signify that we're not to dequeue an event. This will be re-interpreted in // GetClusterNotify. // *pBufferLength = Length; Status = ERROR_MORE_DATA; } else { // pBuffer != NULL // // We need to determine whether the buffer is big enough - that determines // whether we return ERROR_SUCCESS (it is) or ERROR_MORE_DATA (it isn't) // if (Length < *pBufferLength) { // // Success - the buffer is large enough. // Status = ERROR_SUCCESS; } else { // // Failure - the buffer was too small. A buffer was specified, so we need to // return ERROR_MORE_DATA. // *pBufferLength = Length; Status = ERROR_MORE_DATA; } } // if: pBuffer == NULL } // if: pBufferLength == NULL return Status; } //*** GetClusterNotifyCallback