/*++

Copyright (c) 1996-1997  Microsoft Corporation

Module Name:

    fmreg.c

Abstract:

    Object Manager registry query routines for the Failover Manager
    component of the NT Cluster Service.

Author:

    Rod Gamache (rodga) 14-Mar-1996

Revision History:

--*/
#include "fmp.h"
#include <stdlib.h>
#include <search.h>

#define LOG_MODULE FMREG

//
// Global data initialized in this module
//
ULONG   FmpUnknownCount = 0;

//
// Local functions
//

VOID
FmpGroupChangeCallback(
    IN DWORD_PTR    Context1,
    IN DWORD_PTR    Context2,
    IN DWORD        CompletionFilter,
    IN LPCWSTR      RelativeName
    );

VOID
FmpResourceChangeCallback(
    IN DWORD_PTR    Context1,
    IN DWORD_PTR    Context2,
    IN DWORD        CompletionFilter,
    IN LPCWSTR      RelativeName
    );




/////////////////////////////////////////////////////////////////////////////
//
// Configuration Database Access Routines
//
/////////////////////////////////////////////////////////////////////////////

DWORD
FmpRegEnumerateKey(
    IN     HDMKEY     ListKey,
    IN     DWORD      Index,
    OUT    LPWSTR    *Name,
    IN OUT LPDWORD    NameMaxSize
    )

/*++

Routine Description:

Arguments:

Returns:

--*/

{
    DWORD           status;
    FILETIME        fileTime;


    status = DmEnumKey( ListKey,
                        Index,
                        *Name,
                        NameMaxSize,
                        NULL );

    if ( status == ERROR_SUCCESS ) {
        return(ERROR_SUCCESS);
    }

    if ( status == ERROR_MORE_DATA ) {
        PWCHAR   nameString = NULL;
        DWORD    maxSubkeyNameSize = 0;
        DWORD    temp = 0;

        //
        // The name string isn't big enough. Reallocate it.
        //

        //
        // Find out the length of the longest subkey name.
        //
        status = DmQueryInfoKey( ListKey,
                                 &temp,
                                 &maxSubkeyNameSize,
                                 &temp,
                                 &temp,
                                 &temp,
                                 NULL,
                                 &fileTime );

        if ( (status != ERROR_SUCCESS) &&
             (status != ERROR_MORE_DATA) ) {
            ClRtlLogPrint(LOG_NOISE,"[FM] DmQueryInfoKey returned status %1!u!\n",
                status);
            return(status);
        }

        CL_ASSERT(maxSubkeyNameSize != 0);

        //
        // The returned subkey name size does not include the terminating null.
        // It is also an ANSI string count.
        //
        maxSubkeyNameSize *= sizeof(WCHAR);
        maxSubkeyNameSize += sizeof(UNICODE_NULL);

        nameString = LocalAlloc( LMEM_FIXED,
                                 maxSubkeyNameSize );

        if ( nameString == NULL ) {
            ClRtlLogPrint(LOG_NOISE,
                "[FM] Unable to allocate key name buffer of size %1!u!\n",
                maxSubkeyNameSize
                );
            return(ERROR_NOT_ENOUGH_MEMORY);
        }

        LocalFree(*Name);
        *Name = nameString;
        *NameMaxSize = maxSubkeyNameSize;

        status = DmEnumKey( ListKey,
                            Index,
                            *Name,
                            NameMaxSize,
                            NULL );

        CL_ASSERT(status != ERROR_MORE_DATA);
        CL_ASSERT(status != ERROR_NO_MORE_ITEMS);
    }

    return(status);

} // FmpRegEnumerateKey


VOID
FmpPruneGroupOwners(
    IN PFM_GROUP Group
    )
/*++

Routine Description:

    Prunes the entire preferred group list based on the possible
    nodes of each resource in the group.

Arguments:

    Group - Supplies the group object to be pruned

Return Value:

    None.

--*/

{
    PLIST_ENTRY ListEntry;
    PFM_RESOURCE Resource;

    ListEntry = Group->Contains.Flink;
    while (ListEntry != &Group->Contains) {
        Resource = CONTAINING_RECORD(ListEntry,
                                     FM_RESOURCE,
                                     ContainsLinkage);
        FmpPrunePreferredList(Resource);
        ListEntry = ListEntry->Flink;
    }

    return;
}


VOID
FmpPrunePreferredList(
    IN PFM_RESOURCE Resource
    )

/*++

Routine Description:

    Prune out nodes from the preferred owners list, if the resource cannot
    run on that node.

Arguments:

    Resource - Pointer to the resource object with a possible owners list.

Return Value:

    None.

--*/

{
    PFM_GROUP        group;
    PLIST_ENTRY      listEntry;
    PLIST_ENTRY      entry;
    PPREFERRED_ENTRY preferredEntry;
    PPOSSIBLE_ENTRY   possibleEntry;
    DWORD            orderedEntry = 0;

    group = Resource->Group;

    //
    // For each entry in the Preferred list, it must exist in the possible
    // list.
    //

    for ( listEntry = group->PreferredOwners.Flink;
          listEntry != &(group->PreferredOwners);
          ) {

        preferredEntry = CONTAINING_RECORD( listEntry,
                                            PREFERRED_ENTRY,
                                            PreferredLinkage );
        //
        // Scan the Possible owners list in the resource to make sure that
        // the group can run on all of the preferred owners.
        //
        for ( entry = Resource->PossibleOwners.Flink;
              entry != &(Resource->PossibleOwners);
              entry = entry->Flink ) {
            possibleEntry = CONTAINING_RECORD( entry,
                                               POSSIBLE_ENTRY,
                                               PossibleLinkage );
            if ( preferredEntry->PreferredNode == possibleEntry->PossibleNode ) {
                break;
            }
        }

        listEntry = listEntry->Flink;

        //
        // If we got to the end of the possible owners list and didn't find
        // an entry, then remove the current preferred entry.
        //
        if ( entry == &(Resource->PossibleOwners) ) {
            ClRtlLogPrint( LOG_NOISE,
                        "[FM] Removing preferred node %1!ws! because of resource %2!ws!\n",
                        OmObjectId(preferredEntry->PreferredNode),
                        OmObjectId(Resource));

            //
            // If this was an ordered entry, then decrement count.
            //
            if ( orderedEntry < group->OrderedOwners ) {
                --group->OrderedOwners;
            }
            RemoveEntryList( &preferredEntry->PreferredLinkage );
            OmDereferenceObject(preferredEntry->PreferredNode);
            LocalFree(preferredEntry);
            if ( IsListEmpty( &group->PreferredOwners ) ) {
                ClRtlLogPrint( LOG_ERROR,
                            "[FM] Preferred owners list is now empty! No place to run group %1!ws!\n",
                            OmObjectId(group));
            }
        } else {
            orderedEntry++;
        }
    }

} // FmpPrunePreferredList



BOOL
FmpAddNodeToPrefList(
    IN PNM_NODE     Node,
    IN PFM_GROUP    Group
    )
/*++

Routine Description:

    Node enumeration callback for including all remaining nodes
    in a group's preferred owners list.

Arguments:

    Group - a pointer to the group object to add this node as a preferred owner.

    Context2 - Not used

    Node - Supplies the node.

    Name - Supplies the node's name.

Return Value:

    TRUE - to indicate that the enumeration should continue.
    FALSE - to indicate that the enumeration should not continue.

--*/

{
    //if it is already in the list FmpSetPrefferedEntry returns ERROR_SUCCESS
    if ( FmpSetPreferredEntry( Group, Node ) != ERROR_SUCCESS ) {
        return(FALSE);
    }

    return(TRUE);

} // FmpAddNodeToPrefList

BOOL
FmpAddNodeToListCb(
    IN OUT PNM_NODE_ENUM2 *ppNmNodeEnum,
    IN LPDWORD  pdwAllocatedEntries,
    IN PNM_NODE pNode,
    IN LPCWSTR Id
    )

/*++

Routine Description:

    Worker callback routine for the enumeration of nodes.
    This routine adds the specified node to the list that is being
    generated.

Arguments:

    ppNmNodeEnum - The node Enumeration list. Can be an output if a new list is
            allocated.

    EnumData - Supplies the current enumeration data structure.

    Group - The Group object being enumerated.

    Id - The Id of the node object being enumerated.

Returns:

    TRUE - to indicate that the enumeration should continue.

Side Effects:

    Makes the quorum group first in the list.

--*/

{
    PNM_NODE_ENUM2  pNmNodeEnum;
    PNM_NODE_ENUM2  pNewNmNodeEnum;
    DWORD           dwNewAllocated;
    DWORD           dwStatus;



    pNmNodeEnum = *ppNmNodeEnum;

    if ( pNmNodeEnum->NodeCount >= *pdwAllocatedEntries ) 
    {
        //
        // Time to grow the GROUP_ENUM
        //

        dwNewAllocated = *pdwAllocatedEntries + ENUM_GROW_SIZE;
        pNewNmNodeEnum = LocalAlloc(LMEM_FIXED, NODE_SIZE(dwNewAllocated));
        if ( pNewNmNodeEnum == NULL ) 
        {
            dwStatus = ERROR_NOT_ENOUGH_MEMORY;
            CL_UNEXPECTED_ERROR(dwStatus);
            return(FALSE);
        }

        CopyMemory(pNewNmNodeEnum, pNmNodeEnum, NODE_SIZE(*pdwAllocatedEntries));
        *pdwAllocatedEntries = dwNewAllocated;
        *ppNmNodeEnum = pNewNmNodeEnum;
        LocalFree(pNmNodeEnum);
        pNmNodeEnum = pNewNmNodeEnum;
    }

    //dont copy more that the sixe        
    lstrcpyW(pNmNodeEnum->NodeList[pNmNodeEnum->NodeCount].NodeId, Id );
    ++pNmNodeEnum->NodeCount;


    return(TRUE);

} // FmpAddNodeToListCb

int
__cdecl
SortNodesInAscending(
    const PVOID Elem1,
    const PVOID Elem2
    )
{
    PNM_NODE_INFO2 El1 = (PNM_NODE_INFO2)Elem1;
    PNM_NODE_INFO2 El2 = (PNM_NODE_INFO2)Elem2;

    return(lstrcmpiW( El1->NodeId, El2->NodeId ));

}// SortNodesInAsceding


DWORD
FmpEnumNodesById(
    IN DWORD    dwOptions, 
    OUT PNM_NODE_ENUM2 *ppNodeEnum
    )

/*++

Routine Description:

    Enumerates and sorts the list of Groups.

Arguments:

    *ppNodeEnum - Returns the requested objects.

    dwOptions - 
    
Return Value:

    ERROR_SUCCESS if successful.

    Win32 error code on error.

--*/

{
    DWORD               dwStatus;
    PNM_NODE_ENUM2      pNmNodeEnum = NULL;
    DWORD               dwAllocatedEntries;

    //
    // initialize output params to NULL
    //
    *ppNodeEnum = NULL;

    dwAllocatedEntries = ENUM_GROW_SIZE;

    pNmNodeEnum = LocalAlloc( LMEM_FIXED, NODE_SIZE(ENUM_GROW_SIZE) );
    if ( pNmNodeEnum == NULL ) {
        dwStatus = ERROR_NOT_ENOUGH_MEMORY;
        goto error_exit;
    }

    pNmNodeEnum->NodeCount = 0;

    //
    // Enumerate all nodes
    //

    OmEnumObjects( ObjectTypeNode,
                FmpAddNodeToListCb,
                &pNmNodeEnum,
                &dwAllocatedEntries );

    CL_ASSERT( pNmNodeEnum->NodeCount != 0 );
    //
    // Sort the groups by their collating sequence number.
    //
    
    qsort( (PVOID)(&pNmNodeEnum->NodeList[0]),
           (size_t)pNmNodeEnum->NodeCount,
           sizeof(NM_NODE_INFO2),          
           (int (__cdecl *)(const void*, const void*)) SortNodesInAscending
           );

    *ppNodeEnum = pNmNodeEnum;
    return( ERROR_SUCCESS );

error_exit:
    if ( pNmNodeEnum != NULL ) {
        LocalFree( pNmNodeEnum );
    }

    return( dwStatus );

} // FmpEnumNodesById


BOOL
FmpEnumAddAllOwners(
    IN PFM_RESOURCE Resource,
    IN PVOID Context2,
    IN PNM_NODE Node,
    IN LPCWSTR Name
    )
/*++

Routine Description:

    Node enumeration callback for adding all nodes to a resource's
    list of possible nodes.

Arguments:

    Resource - a pointer to the resource object to add this node as a possible owner.

    Context2 - Not used

    Node - Supplies the node.

    Name - Supplies the node's name.

Return Value:

    TRUE - to indicate that the enumeration should continue.
    FALSE - to indicate that the enumeration should not continue.

--*/

{
    if ( !Resource->PossibleList ) {
        FmpAddPossibleEntry(Resource, Node);
    }
    return(TRUE);

} // FmpEnumAddAllOwners


DWORD
FmpQueryGroupNodes(
    IN PFM_GROUP Group,
    IN HDMKEY hGroupKey
    )
/*++

Routine Description:

    Rebuilds and orders the list of preferred nodes associated with
    a group.

Arguments:

    Group - Supplies the group whose list of preferred nodes should
            be rebuilt.

    hGroupKey - Supplies a handle to the group's registry key

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{
    LPWSTR          preferredOwnersString = NULL;
    DWORD           preferredOwnersStringSize = 0;
    DWORD           preferredOwnersStringMaxSize = 0;
    DWORD           mszStringIndex;
    PPREFERRED_ENTRY preferredEntry;
    DWORD           status;
    PLIST_ENTRY     listEntry;
    PNM_NODE_ENUM2  pNmNodeEnum = NULL;
    PNM_NODE        pNmNode;
    DWORD           i;
    //
    // First, delete the old list.
    //
    while ( !IsListEmpty(&Group->PreferredOwners) ) {
        listEntry = Group->PreferredOwners.Flink;
        preferredEntry = CONTAINING_RECORD( listEntry,
                                            PREFERRED_ENTRY,
                                            PreferredLinkage );
        RemoveEntryList( &preferredEntry->PreferredLinkage );
        OmDereferenceObject( preferredEntry->PreferredNode );
        LocalFree( preferredEntry );
    }
    Group->OrderedOwners = 0;

    CL_ASSERT ( IsListEmpty(&Group->PreferredOwners) );

    status = DmQueryMultiSz( hGroupKey,
                             CLUSREG_NAME_GRP_PREFERRED_OWNERS,
                             &preferredOwnersString,
                             &preferredOwnersStringMaxSize,
                             &preferredOwnersStringSize );

    if ( status == NO_ERROR ) {

        //
        // Now Create the Preferred Owners list.
        //

        for ( mszStringIndex = 0; ; mszStringIndex++ ) {
            LPCWSTR     nameString;
            PNM_NODE    preferredNode;

            nameString = ClRtlMultiSzEnum( preferredOwnersString,
                                           preferredOwnersStringSize/sizeof(WCHAR),
                                           mszStringIndex );

            if ( nameString == NULL ) {
                break;
            }

            //
            // Create the Preferred Owners List entry
            //

            preferredEntry = LocalAlloc( LMEM_FIXED, sizeof(PREFERRED_ENTRY) );

            if ( preferredEntry == NULL ) {
                status = ERROR_NOT_ENOUGH_MEMORY;
                return(status);
            }

            //
            // Create the preferred owners. This will implicitly create
            // additional reference required for the preferred owner nodes.
            //

            ClRtlLogPrint(LOG_NOISE,
                       "[FM] Group %1!ws! preferred owner %2!ws!.\n",
                       OmObjectId(Group),
                       nameString);

            preferredNode = OmReferenceObjectById( ObjectTypeNode,
                                                   nameString );

            if ( preferredNode == NULL ) {
                LocalFree(preferredEntry);
                status = GetLastError();
                ClRtlLogPrint(LOG_NOISE,
                           "[FM] Failed to find node %1!ws! for Group %2!ws!\n",
                           nameString,
                           OmObjectId(Group));
            } else {
                Group->OrderedOwners++;
                preferredEntry->PreferredNode = preferredNode;
                InsertTailList( &Group->PreferredOwners,
                                &preferredEntry->PreferredLinkage );
            }

        }
        LocalFree( preferredOwnersString );
    }

    //
    // We now include all remaining nodes in the preferred owners list.
    //
    // Every node must maintain the same ordering for the preferred list
    // for the multi-node cluster to work
    //
    status = FmpEnumNodesById( 0, &pNmNodeEnum );

    if ( status != ERROR_SUCCESS )
    {
        CL_UNEXPECTED_ERROR( status );
        ClRtlLogPrint(LOG_UNUSUAL, 
        	   "[FM] FmpQueryGroupNodes: FmpEnumNodesById failed, status = %1!u!\r\n",
        	    status);
        // return error                    
    }

    for ( i=0; i<pNmNodeEnum->NodeCount; i++ )
    {
        pNmNode = OmReferenceObjectById( ObjectTypeNode, 
                        pNmNodeEnum->NodeList[i].NodeId );
        CL_ASSERT( pNmNode != NULL );
        FmpAddNodeToPrefList( pNmNode, Group );
        OmDereferenceObject( pNmNode );     
    }

    //
    // Now prune out all the unreachable nodes.
    //
    FmpPruneGroupOwners( Group );

    //
    //  Chittur Subbaraman (chitturs) - 12/11/98
    //
    //  Free the memory allocated for pNmNodeEnum.
    //  (Fix memory leak)
    //
    LocalFree( pNmNodeEnum );

    return( ERROR_SUCCESS );

} // FmpQueryGroupNodes



DWORD
WINAPI
FmpQueryGroupInfo(
    IN PVOID Object,
    IN BOOL  Initialize
    )

/*++

Routine Description:

    Queries Group info from the registry when creating a Group Object.

Arguments:

    Object - A pointer to the Group object being created.

    Initialize - TRUE if the resource objects should be initialized. FALSE
                 otherwise.

Return Value:

    ERROR_SUCCESS if successful.

    Win32 error code otherwise.

--*/

{
    PFM_GROUP       Group = (PFM_GROUP)Object;
    PFM_RESOURCE    Resource;
    DWORD           status;
    LPWSTR          containsString = NULL;
    DWORD           containsStringSize = 0;
    DWORD           containsStringMaxSize = 0;
    DWORD           temp;
    DWORD           mszStringIndex;
    DWORD           failoverThreshold = CLUSTER_GROUP_DEFAULT_FAILOVER_THRESHOLD;
    DWORD           failoverPeriod = CLUSTER_GROUP_DEFAULT_FAILOVER_PERIOD;
    DWORD           autoFailbackType = CLUSTER_GROUP_DEFAULT_AUTO_FAILBACK_TYPE;
    DWORD           zero = 0;
    PLIST_ENTRY     listEntry;
    HDMKEY          groupKey;
    DWORD           groupNameStringMaxSize = 0;
    DWORD           groupNameStringSize = 0;
    LPWSTR          groupName;
    PPREFERRED_ENTRY preferredEntry;
    DWORD           dwBufferSize = 0;
    DWORD           dwStringSize;


    //
    // Initialize the Group object from the registry info.
    //
    if ( Group->Initialized ) {
        return(ERROR_SUCCESS);
    }

    ClRtlLogPrint(LOG_NOISE,
               "[FM] Initializing group %1!ws! from the registry.\n",
                OmObjectId(Group));

    //
    // Open the group key.
    //
    groupKey = DmOpenKey( DmGroupsKey,
                          OmObjectId(Group),
                          MAXIMUM_ALLOWED );

    if ( groupKey == NULL ) {
        status = GetLastError();
        ClRtlLogPrint(LOG_UNUSUAL,
                   "[FM] Unable to open group key %1!ws!, %2!u!\n",
                    OmObjectId(Group),
                    status);

        return(status);
    }

    //
    // Read the required group values. The strings will be allocated
    // by the DmQuery* functions.
    //

    //
    // Get the Name.
    //
    status = DmQuerySz( groupKey,
                        CLUSREG_NAME_GRP_NAME,
                        &groupName,
                        &groupNameStringMaxSize,
                        &groupNameStringSize );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to read name for Group %1!ws!\n",
                    OmObjectId(Group));
        goto error_exit;
    }

    status = OmSetObjectName( Group, groupName );

    if ( status != ERROR_SUCCESS ) {
        ClRtlLogPrint(LOG_ERROR,
                   "[FM] Unable to set name %1!ws! for group %2!ws!, error %3!u!.\n",
                    groupName,
                    OmObjectId(Group),
                    status );
        goto error_exit;
    }

    ClRtlLogPrint(LOG_NOISE,
               "[FM] Name for Group %1!ws! is '%2!ws!'.\n",
                OmObjectId(Group),
                groupName);

    LocalFree(groupName);
    //
    // Get the PersistentState.
    //
    status = DmQueryDword( groupKey,
                           CLUSREG_NAME_GRP_PERSISTENT_STATE,
                           &temp,
                           &zero );

    //
    // If the group state is non-zero then we go online.
    //
    if ( temp ) {
        Group->PersistentState = ClusterGroupOnline;
    } else {
        Group->PersistentState = ClusterGroupOffline;
    }

    //
    // Get the OPTIONAL PreferredOwners list.
    // *** NOTE *** This MUST be done before processing the contains list!
    //
    status = FmpQueryGroupNodes(Group, groupKey);
    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_UNUSUAL,"[FM] Error %1!d! creating preferred owners list\n",status);
        goto error_exit;
    }


    //
    // Get the Contains string.
    //
    status = DmQueryMultiSz( groupKey,
                             CLUSREG_NAME_GRP_CONTAINS,
                             &containsString,
                             &containsStringMaxSize,
                             &containsStringSize );

    if ( status != NO_ERROR ) {
        if ( status != ERROR_FILE_NOT_FOUND ) {
            ClRtlLogPrint(LOG_UNUSUAL,
                       "[FM] Unable to read Contains for group %1!ws!\n",
                       OmObjectId(Group));
        }
    } else {
        //
        // Now Create the Contains list.
        //

        for ( mszStringIndex = 0; ; mszStringIndex++ ) {
            LPCWSTR      nameString;
            PFM_RESOURCE containedResource;

            nameString = ClRtlMultiSzEnum( containsString,
                                           containsStringSize/sizeof(WCHAR),
                                           mszStringIndex );

            if ( nameString == NULL ) {
                break;
            }

            ClRtlLogPrint(LOG_NOISE,
                       "[FM] Group %1!ws! contains Resource %2!ws!.\n",
                       OmObjectId(Group),
                       nameString);

            //
            // Try to create the object.
            //
            FmpAcquireResourceLock();
            FmpAcquireLocalGroupLock( Group );

            containedResource = FmpCreateResource( Group,
                                                   nameString,
                                                   NULL,
                                                   Initialize );
            FmpReleaseLocalGroupLock( Group );
            FmpReleaseResourceLock();

            //
            // Check if we got a resource.
            //
            if ( containedResource == NULL ) {
                //
                // This group claims to contain a non-existent resource.
                // Log an error, but keep going. This should not tank the
                // whole group. Also, let the arbitration code know about
                // the failure of a resource.
                //
                Group->InitFailed = TRUE;
                ClRtlLogPrint(LOG_UNUSUAL,
                           "[FM] Failed to find resource %1!ws! for Group %2!ws!\n",
                           nameString,
                           OmObjectId(Group));
            }
        }
        LocalFree(containsString);

    }

    //
    // Get the AutoFailbackType.
    //

    status = DmQueryDword( groupKey,
                           CLUSREG_NAME_GRP_FAILBACK_TYPE,
                           &temp,
                           &autoFailbackType );

    //
    // Verify that AutoFailbackType is okay.
    //

    if ( temp >= FailbackMaximum ) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Illegal value for AutoFailbackType on %1!ws!, setting to default\n",
                   OmObjectId(Group));
        temp = autoFailbackType;
    }

    Group->FailbackType = (UCHAR)temp;

    //
    // Get the FailbackWindowStart.
    //
    status = DmQueryDword( groupKey,
                           CLUSREG_NAME_GRP_FAILBACK_WIN_START,
                           &temp,
                           &zero );

    //
    // Verify that FailbackWindowStart is okay.
    //
    if ( temp > 24 ) {
        if ( temp != CLUSTER_GROUP_DEFAULT_FAILBACK_WINDOW_START ) {
            ClRtlLogPrint(LOG_NOISE,
                      "[FM] Illegal value for FailbackWindowStart on %1!ws!,setting to default\n",
                      OmObjectId(Group));
            temp = zero;
        }
    }
    Group->FailbackWindowStart = (UCHAR)temp;

    //
    // Get the FailbackWindowEnd.
    //
    status = DmQueryDword( groupKey,
                           CLUSREG_NAME_GRP_FAILBACK_WIN_END,
                           &temp,
                           &zero );

    //
    // Verify that FailbackWindowEnd is okay.
    //

    if ( temp > 24 ) {
        if ( temp != CLUSTER_GROUP_DEFAULT_FAILBACK_WINDOW_END ) {
            ClRtlLogPrint(LOG_NOISE,
                       "[FM] Illegal value for FailbackWindowEnd on %1!ws!, setting to default\n",
                       OmObjectId(Group));
            temp = zero;
        }
    }
    Group->FailbackWindowEnd = (UCHAR)temp;

    //
    // Get the FailoverPeriod.
    //
    status = DmQueryDword( groupKey,
                           CLUSREG_NAME_GRP_FAILOVER_PERIOD,
                           &temp,
                           &failoverPeriod );

    //
    // Verify that FailoverPeriod is okay. Take any value up to UCHAR max.
    // In theory we could take any value... but in practice we have to convert
    // this time to milliseconds (currently). That means that 1193 hours can
    // fit in a DWORD - so that is the maximum we can take. (We are limited
    // because we use GetTickCount, which returns a DWORD in milliseconds.)
    //

    if ( temp > CLUSTER_GROUP_MAXIMUM_FAILOVER_PERIOD ) {      // Keep it positive?
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Illegal value for FailolverPeriod on %1!ws!. Max is 1193\n",
                   OmObjectId(Group));
        temp = failoverPeriod;                   
    } 

    Group->FailoverPeriod = (UCHAR)temp;
    

    //
    // Get the FailoverThreshold.
    //
    status = DmQueryDword( groupKey,
                           CLUSREG_NAME_GRP_FAILOVER_THRESHOLD,
                           &(Group->FailoverThreshold),
                           &failoverThreshold );

    //
    // Verify that FailoverThreshold is okay. Take any value.
    //

    //
    // Get the AntiAffinityClassName property if present.
    //
    status = DmQueryMultiSz( groupKey,
                             CLUSREG_NAME_GRP_ANTI_AFFINITY_CLASS_NAME,
                             &Group->lpszAntiAffinityClassName,
                             &dwBufferSize,
                             &dwStringSize );

    //
    //  Handle the case in which the string is empty.
    //
    if ( ( status == ERROR_SUCCESS ) &&
         ( Group->lpszAntiAffinityClassName != NULL ) &&
         ( Group->lpszAntiAffinityClassName[0] == L'\0' ) )
    {
        LocalFree( Group->lpszAntiAffinityClassName );
        Group->lpszAntiAffinityClassName = NULL;
    }
         
    //
    // We're done. We should only get here if Group->Initialized is FALSE.
    //
    CL_ASSERT( Group->Initialized == FALSE );
    Group->Initialized = TRUE;
    Group->RegistryKey = groupKey;

    //
    // Now register for any changes to the resource key.
    //

    status = DmNotifyChangeKey(
                    groupKey,
                    (DWORD) CLUSTER_CHANGE_ALL,
                    FALSE,              // Only watch the top of the tree
                    &Group->DmRundownList,
                    FmpGroupChangeCallback,
                    (DWORD_PTR)Group,
                    0 );

    if ( status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Error registering for DM change notify on group %1!ws!, error %2!u!.\n",
                    OmObjectId(Group),
                    status);
        goto error_exit;
    }

    return(ERROR_SUCCESS);


error_exit:

    Group->Initialized = FALSE;
    Group->RegistryKey = NULL;

    DmCloseKey(groupKey);

    //
    // Cleanup any contained resources
    //
    while ( !IsListEmpty(&Group->Contains) ) {
        listEntry = RemoveHeadList(&Group->Contains);
        Resource = CONTAINING_RECORD(listEntry, FM_RESOURCE, ContainsLinkage);
        OmDereferenceObject(Resource);
    }

    //
    // Cleanup any preferred nodes
    //
    while ( !IsListEmpty(&Group->PreferredOwners) ) {
        listEntry = RemoveHeadList(&Group->PreferredOwners);
        preferredEntry = CONTAINING_RECORD(listEntry, PREFERRED_ENTRY, PreferredLinkage);
        OmDereferenceObject(preferredEntry->PreferredNode);
        LocalFree(preferredEntry);
    }

    return(status);

} // FmpQueryGroupInfo



DWORD
WINAPI
FmpFixupGroupInfo(
    IN PFM_GROUP Group
    )

/*++

Routine Description:

    Re-queries Group info from the registry to fixup items that may have
    changed since the quorum resource (and the Group that it is in) was
    first created.

    This routine exists because we may have created the quorum resource
    (and its Group) early in the 'life' of the cluster, before all the node
    objects (for example) were created. We then would have failed generating
    the list of possible owners for the resource. This in turn would have
    caused some entries from the preferred list to get pruned. We need to
    redo this operation again here.

Arguments:

    Group - A pointer to the Group object to fix up.

Return Value:

    ERROR_SUCCESS if successful.

    Win32 error code otherwise.

Notes:

    It is assumed that the quorum resource fixup already has happened.

--*/

{
    DWORD   status;

    status = FmpQueryGroupNodes(Group, Group->RegistryKey);

    return(status);

} // FmpFixupGroupInfo



DWORD
WINAPI
FmpQueryResourceInfo(
    IN PVOID Object,
    IN BOOL  Initialize
    )

/*++

Routine Description:

    Queries Resource info from the registry when creating a Resource Object.

Arguments:

    Object - A pointer to the Resource object being created.

    Initialize - TRUE if the resource should be fully initialized.
                 FALSE otherwise.

Return Value:

    ERROR_SUCCESS if successful.

    Win32 error code otherwise.

--*/

{
    PFM_RESOURCE    Resource = (PFM_RESOURCE)Object;
    DWORD           status;
    DWORD           dllNameStringSize = 0;
    DWORD           dllNameStringMaxSize = 0;
    LPWSTR          resourceTypeString = NULL;
    DWORD           resourceTypeStringMaxSize = 0;
    DWORD           resourceTypeStringSize = 0;
    DWORD           dependenciesStringMaxSize = 0;
    DWORD           restartThreshold = CLUSTER_RESOURCE_DEFAULT_RESTART_THRESHOLD;
    DWORD           restartPeriod = CLUSTER_RESOURCE_DEFAULT_RESTART_PERIOD;
    DWORD           pendingTimeout = CLUSTER_RESOURCE_DEFAULT_PENDING_TIMEOUT;
    DWORD           RetryPeriodOnFailure = CLUSTER_RESOURCE_DEFAULT_RETRY_PERIOD_ON_FAILURE;
    DWORD           defaultRestartAction = RestartGroup;
    DWORD           DefaultExFlags = 0;
    DWORD           zero = 0;
    DWORD           temp;
    DWORD           separateMonitor;
    HDMKEY          resourceKey;
    DWORD           resourceNameStringMaxSize = 0;
    DWORD           resourceNameStringSize = 0;
    LPWSTR          resourceName = NULL;
    LPWSTR          possibleOwnersString = NULL;
    DWORD           possibleOwnersStringSize = 0;
    DWORD           possibleOwnersStringMaxSize = 0;
    DWORD           mszStringIndex;
    PPOSSIBLE_ENTRY possibleEntry;
    PLIST_ENTRY     listEntry;
    WCHAR           unknownName[] = L"_Unknown9999";
    DWORD           nameSize = 0;
    DWORD           stringSize;

    //if the key is non null, this resource has already been initialized
    if (Resource->RegistryKey != NULL)
        return(ERROR_SUCCESS);

    ClRtlLogPrint(LOG_NOISE,
               "[FM] Initializing resource %1!ws! from the registry.\n",
                OmObjectId(Resource));

    //
    // Begin initializing the resource from the registry.
    //
    //
    // Open the resource key.
    //
    resourceKey = DmOpenKey( DmResourcesKey,
                             OmObjectId(Resource),
                             MAXIMUM_ALLOWED );

    if ( resourceKey == NULL ) {
        status = GetLastError();
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to open resource key %1!ws!, %2!u!\n",
                   OmObjectId(Resource),
                   status);
        return(ERROR_INVALID_NAME);
    }

    //
    // Read the required resource values. The strings will be allocated
    // by the DmQuery* functions.
    //

    //
    // Get the Name.
    //
    status = DmQuerySz( resourceKey,
                        CLUSREG_NAME_RES_NAME,
                        &resourceName,
                        &resourceNameStringMaxSize,
                        &resourceNameStringSize );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to read name for resource %1!ws!\n",
                   OmObjectId(Resource));
        if ( OmObjectName( Resource ) == NULL ) {
            wsprintf( unknownName,
                      L"_Unknown%u",
                      InterlockedIncrement( &FmpUnknownCount ));
            status = OmSetObjectName( Resource, unknownName );
        } else {
            status = ERROR_SUCCESS;
        }
    } else {
        status = OmSetObjectName( Resource, resourceName );
    }
    if ( status != ERROR_SUCCESS ) {
        ClRtlLogPrint(LOG_ERROR,
                   "[FM] Unable to set name %1!ws! for resource %2!ws!, error %3!u!.\n",
                   resourceName,
                   OmObjectId(Resource),
                   status );
        LocalFree(resourceName);
        status = ERROR_INVALID_NAME;
        goto error_exit;
    }

    ClRtlLogPrint(LOG_NOISE,
               "[FM] Name for Resource %1!ws! is '%2!ws!'.\n",
                OmObjectId(Resource),
                resourceName);

    LocalFree(resourceName);

    //
    // Get the dependencies list.
    //

    status = DmQueryMultiSz( resourceKey,
                             CLUSREG_NAME_RES_DEPENDS_ON,
                             &(Resource->Dependencies),
                             &dependenciesStringMaxSize,
                             &(Resource->DependenciesSize) );

    if (status != NO_ERROR) {
        if ( status != ERROR_FILE_NOT_FOUND ) {
            ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to read Dependencies for resource %1!ws!\n",
                   OmObjectId(Resource));
        }
    }

    //
    // Get the OPTIONAL PossibleOwners list.
    //
    // We do this here, because we must have a possible owners list for the
    // CluAdmin to start the resource.
    //

    status = DmQueryMultiSz( resourceKey,
                             CLUSREG_NAME_RES_POSSIBLE_OWNERS,
                             &possibleOwnersString,
                             &possibleOwnersStringMaxSize,
                             &possibleOwnersStringSize );

    if ( status == NO_ERROR ) {

        //
        // Now Create the Possible Owners list.
        //

        for ( mszStringIndex = 0; ; mszStringIndex++ ) {
            LPCWSTR     nameString;
            PNM_NODE    possibleNode;

            nameString = ClRtlMultiSzEnum( possibleOwnersString,
                                           possibleOwnersStringSize/sizeof(WCHAR),
                                           mszStringIndex );

            if ( nameString == NULL ) {
                break;
            }
            possibleNode = OmReferenceObjectById( ObjectTypeNode,
                                                  nameString );

            if ( possibleNode == NULL ) {
                ClRtlLogPrint(LOG_NOISE,
                           "[FM] Warning, failed to find node %1!ws! for Resource %2!ws!\n",
                           nameString,
                           OmObjectId(Resource));
            } else {
                Resource->PossibleList = TRUE;
                status = FmpAddPossibleEntry(Resource, possibleNode);
                OmDereferenceObject(possibleNode);
                if (status != ERROR_SUCCESS) {
                    goto error_exit;
                }
            }
        }
        LocalFree(possibleOwnersString);

        //
        // Now prune out unusable nodes from the preferred owners list.
        //
        FmpPrunePreferredList( Resource );

    } else {
        //
        // No possible owners value was specified. Add all the nodes
        // to the possible owners list. Note there is no point in pruning
        // the preferred list after this since this resource can run
        // anywhere.
        //
        OmEnumObjects( ObjectTypeNode,
                       FmpEnumAddAllOwners,
                       Resource,
                       NULL );
    }

    //
    // Get the resource type.
    //
    status = DmQuerySz( resourceKey,
                        CLUSREG_NAME_RES_TYPE,
                        &resourceTypeString,
                        &resourceTypeStringMaxSize,
                        &resourceTypeStringSize );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to read ResourceType for resource %1!ws!\n",
                   OmObjectId(Resource));
        goto error_exit;
    }

    //
    // Open (and reference) the resource type.
    //
    if (Resource->Type == NULL)
    {
        Resource->Type = OmReferenceObjectById( ObjectTypeResType,
                                            resourceTypeString );
    }                                            
    if (Resource->Type == NULL) {

        PFM_RESTYPE pResType;
        //
        // If we can't find a resource type, then try to create it.
        //
        pResType = FmpCreateResType(resourceTypeString );

        if (pResType == NULL) {
            status = ERROR_INVALID_PARAMETER;
            LocalFree(resourceTypeString);
            goto error_exit;
        }

        //bump the ref count before saving a pointer to it in the
        //resource structure.
        OmReferenceObject(pResType);
        Resource->Type = pResType;
    }

    LocalFree(resourceTypeString);

    if ( !Initialize ) {
        //
        // We're not supposed to fully initialize the resource. This is
        // when we're early in the init process. We need to keep the registry
        // key closed when leaving.
        //
        DmCloseKey(resourceKey);
        return(ERROR_SUCCESS);
    }


    //
    // Get the IsAlive poll interval
    //
    CL_ASSERT( Resource->Type->IsAlivePollInterval != 0 );
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_IS_ALIVE,
                           &Resource->IsAlivePollInterval,
                           &Resource->Type->IsAlivePollInterval );

    if ( status != NO_ERROR ) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to read IsAlivePollInterval for resource %1!ws!. Error %2!u!\n",
                   OmObjectId(Resource),
                   status);

        goto error_exit;
    }

    if ( Resource->IsAlivePollInterval == CLUSTER_RESOURCE_USE_DEFAULT_POLL_INTERVAL ) {
        Resource->IsAlivePollInterval = Resource->Type->IsAlivePollInterval;
    }

    //
    // Get the LooksAlive poll interval
    //
    CL_ASSERT( Resource->Type->LooksAlivePollInterval != 0 );
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_LOOKS_ALIVE,
                           &Resource->LooksAlivePollInterval,
                           &Resource->Type->LooksAlivePollInterval );

    if ( status != NO_ERROR ) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to read LooksAlivePollInterval for resource %1!ws!. Error %2!u!\n",
                   OmObjectId(Resource),
                   status);
        goto error_exit;
    }

    if ( Resource->LooksAlivePollInterval == CLUSTER_RESOURCE_USE_DEFAULT_POLL_INTERVAL ) {
        Resource->LooksAlivePollInterval = Resource->Type->LooksAlivePollInterval;
    }

    //
    // Get the current persistent state of the resource.
    //
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_PERSISTENT_STATE,
                           &temp,
                           NULL );

    //
    // Save the current resource state.
    //

    if ( ( status == ERROR_FILE_NOT_FOUND )  || 
         ( ( status == ERROR_SUCCESS ) && ( temp == CLUSTER_RESOURCE_DEFAULT_PERSISTENT_STATE ) ) ) {
        switch ( Resource->Group->PersistentState ) {
        case ClusterGroupOnline:
            Resource->PersistentState = ClusterResourceOnline;
            break;
        case ClusterGroupOffline:
            Resource->PersistentState = ClusterResourceOffline;
            break;
        default:
            break;
        }
    } else if ( status != NO_ERROR ) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to read PersistentState for resource %1!ws!. Error %2!u!\n",
                   OmObjectId(Resource),
                   status);
        goto error_exit;
    } else if ( temp ) {
        Resource->PersistentState = ClusterResourceOnline;
    } else {
        Resource->PersistentState = ClusterResourceOffline;
    }

    //
    // Determine the monitor to run this in.
    //
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_SEPARATE_MONITOR,
                           &separateMonitor,
                           &zero );
    if ( separateMonitor ) {
        Resource->Flags |= RESOURCE_SEPARATE_MONITOR;
    }

    //
    // Get the RestartThreshold.
    //

    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_RESTART_THRESHOLD,
                           &Resource->RestartThreshold,
                           &restartThreshold );

    // Verify the RestartThreshold. Take any value.

    //
    // Get the RestartPeriod.
    //

    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_RESTART_PERIOD,
                           &Resource->RestartPeriod,
                           &restartPeriod );

    // Verify the RestartPeriod. Take any value.

    //
    // Get the RestartAction.
    //

    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_RESTART_ACTION,
                           &Resource->RestartAction,
                           &defaultRestartAction );

    // Verify the RestartAction.

    if ( Resource->RestartAction >= RestartMaximum ) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Illegal RestartAction for resource %1!ws!\n",
                   OmObjectId(Resource));
        goto error_exit;
    }

    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_RETRY_PERIOD_ON_FAILURE,
                           &Resource->RetryPeriodOnFailure,
                           &RetryPeriodOnFailure );

    // make sure that RetryPeriodOnFailure >= RestartPeriod
    if (Resource->RetryPeriodOnFailure < Resource->RestartPeriod)
    {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Specified RetryPeriodOnFailure value is less than RestartPeriod value - setting RetryPeriodOnFailure equal to RestartPeriod \n");
        Resource->RetryPeriodOnFailure = Resource->RestartPeriod;              
        
    }    

                           
    //
    // Get the extrinsic Flags
    //
    DefaultExFlags = 0;
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_FLAGS,
                           &Resource->ExFlags,
                           &DefaultExFlags );

    if ( status != NO_ERROR ) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to read Extrinsic Flags for resource %1!ws!. Error %2!u!\n",
                   OmObjectId(Resource),
                   status);

        goto error_exit;
    }

    //
    // Get the PendingTimeout value.
    //

    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_PENDING_TIMEOUT,
                           &Resource->PendingTimeout,
                           &pendingTimeout );

    // Verify the PendingTimeout. Take any value.

    //
    // Now register for any changes to the resource key.
    //

    if (IsListEmpty(&Resource->DmRundownList))
    {
        status = DmNotifyChangeKey(
                    resourceKey,
                    (DWORD) CLUSTER_CHANGE_ALL,
                    FALSE,              // Only watch the top of the tree
                    &Resource->DmRundownList,
                    FmpResourceChangeCallback,
                    (DWORD_PTR)Resource,
                    0 );

        if ( status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_NOISE,
                   "[FM] Error registering for DM change notify on resource %1!ws!, error %2!u!.\n",
                    OmObjectId(Resource),
                    status);
            goto error_exit;
        }
    }
    //
    // Get the DebugPrefix string... this is on the resource type.
    //
    status = DmQuerySz( resourceKey,
                        CLUSREG_NAME_RES_DEBUG_PREFIX,
                        &Resource->DebugPrefix,
                        &nameSize,
                        &stringSize );

    //
    // Finally save the resource key for registry updates of the
    // PersistentState.
    //
    Resource->RegistryKey = resourceKey;

    return(ERROR_SUCCESS);


error_exit:

    DmCloseKey(resourceKey);

    if ( Resource->Type != NULL ) {
        OmDereferenceObject(Resource->Type);
    }

    //
    // Cleanup any dependencies
    //
    if ( Resource->Dependencies != NULL ) {
        LocalFree(Resource->Dependencies);
        Resource->Dependencies = NULL;
    }

    //
    // Cleanup any possible nodes
    //
    while ( !IsListEmpty(&Resource->PossibleOwners) ) {
        listEntry = RemoveHeadList(&Resource->PossibleOwners);
        possibleEntry = CONTAINING_RECORD(listEntry, POSSIBLE_ENTRY, PossibleLinkage);
        OmDereferenceObject(possibleEntry->PossibleNode);
        LocalFree(possibleEntry);
    }

    return(status);

} // FmpQueryResourceInfo



DWORD
WINAPI
FmpFixupResourceInfo(
    IN PFM_RESOURCE Resource
    )

/*++

Routine Description:

    Re-queries Resource info from the registry to fixup items that may have
    changed since the quorum resource was first created.

    This routine exists because we may have created the quorum resource early
    in the 'life' of the cluster, before all the node objects (for example)
    were created. We then would have failed generating the list of possible
    owners for the resource. In FmpQueryResourceInfo, we treat failures to
    find node objects as non-fatal errors, which we will now cleanup.

Arguments:

    Resource - A pointer to the Resource object to fix up.

Return Value:

    ERROR_SUCCESS if successful.

    Win32 error code otherwise.

--*/

{
    LPWSTR          possibleOwnersString = NULL;
    DWORD           possibleOwnersStringSize = 0;
    DWORD           possibleOwnersStringMaxSize = 0;
    DWORD           mszStringIndex;
    DWORD           status;


    if ( Resource->RegistryKey == NULL ) {
        return(ERROR_NOT_READY);
    }

    //
    // Get the OPTIONAL PossibleOwners list.
    //

    status = DmQueryMultiSz( Resource->RegistryKey,
                             CLUSREG_NAME_RES_POSSIBLE_OWNERS,
                             &possibleOwnersString,
                             &possibleOwnersStringMaxSize,
                             &possibleOwnersStringSize );

    if ( status == NO_ERROR ) {

        //
        // Now Create the Possible Owners list.
        //

        for ( mszStringIndex = 0; ; mszStringIndex++ ) {
            LPCWSTR     nameString;
            PNM_NODE    possibleNode;

            nameString = ClRtlMultiSzEnum( possibleOwnersString,
                                           possibleOwnersStringSize/sizeof(WCHAR),
                                           mszStringIndex );

            if ( nameString == NULL ) {
                break;
            }
            possibleNode = OmReferenceObjectById( ObjectTypeNode,
                                                  nameString );

            if ( possibleNode == NULL ) {
                ClRtlLogPrint(LOG_NOISE,
                           "[FM] Warning, failed to find node %1!ws! for Resource %2!ws!\n",
                           nameString,
                           OmObjectId(Resource));
            } else {
                Resource->PossibleList = TRUE;
                status = FmpAddPossibleEntry(Resource, possibleNode);
                OmDereferenceObject(possibleNode);
                if (status != ERROR_SUCCESS) {
                    return(status);
                }
            }
        }
        LocalFree(possibleOwnersString);

        //
        // Now prune out unusable nodes from the preferred owners list.
        //
        FmpPrunePreferredList( Resource );

    } else {
        //
        // No possible owners value was specified. Add all the nodes
        // to the possible owners list. Note there is no point in pruning
        // the preferred list after this since this resource can run
        // anywhere.
        //
        OmEnumObjects( ObjectTypeNode,
                       FmpEnumAddAllOwners,
                       Resource,
                       NULL );

    }

    return(ERROR_SUCCESS);

} // FmpFixupQuorumResourceInfo



DWORD
WINAPI
FmpQueryResTypeInfo(
    IN PVOID Object
    )

/*++

Routine Description:

    Queries Resource Type info from the registry when creating a ResType Object.

Arguments:

    Object - A pointer to the Resource Type object being created.

Return Value:

    ERROR_SUCCESS if successful.

    Win32 error code otherwise.

--*/

{
    PFM_RESTYPE     resType = (PFM_RESTYPE)Object;
    DWORD           status;
    DWORD           dwSize = 0;
    DWORD           stringSize;
    HDMKEY          resTypeKey;
    DWORD           temp;
    LPWSTR          pmszPossibleNodes = NULL;
    
    //
    // Open the resource type key.
    //
    resTypeKey = DmOpenKey( DmResourceTypesKey,
                            OmObjectId(resType),
                            MAXIMUM_ALLOWED );

    if ( resTypeKey == NULL ) {
        status = GetLastError();
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to open resource type key %1!ws!, %2!u!\n",
                   OmObjectId(resType),
                   status);

        return(status);
    }

    //
    // Read the required resource type DLL name. The strings will be allocated
    // by the DmQuery* functions.
    //

    status = DmQuerySz( resTypeKey,
                        CLUSREG_NAME_RESTYPE_DLL_NAME,
                        &resType->DllName,
                        &dwSize,
                        &stringSize );
    if ( status != NO_ERROR ) {
        if ( status == ERROR_FILE_NOT_FOUND ) {
            ClRtlLogPrint(LOG_CRITICAL,
                          "[FM] The DllName value for the %1!ws! resource type does not exist. "
                          "Resources of this type will not be monitored.\n",
                          OmObjectId(resType));
        }
        else {
            ClRtlLogPrint(LOG_CRITICAL,
                          "[FM] The DllName value for the %1!ws! resource type could not be read "
                          "from the registry. Resources of this type will not be monitored. "
                          "The error was %2!d!.\n",
                          OmObjectId(resType),
                          status);
        }

        goto error_exit;
    }


    //
    // Get the optional LooksAlive poll interval
    //
    status = DmQueryDword( resTypeKey,
                           CLUSREG_NAME_RESTYPE_LOOKS_ALIVE,
                           &resType->LooksAlivePollInterval,
                           NULL );

    if ( status != NO_ERROR ) {
        if ( status == ERROR_FILE_NOT_FOUND ) {
            resType->LooksAlivePollInterval = CLUSTER_RESTYPE_DEFAULT_LOOKS_ALIVE;
        } else {
            ClRtlLogPrint(LOG_CRITICAL,
                          "[FM] The LooksAlive poll interval for the %1!ws! resource type could "
                          "not be read from the registry. Resources of this type will not be "
                          "monitored. The error was %2!d!.\n",
                          OmObjectId(resType),
                          status);
            goto error_exit;
        }
    }

    //
    // Get the optional IsAlive poll interval
    //
    status = DmQueryDword( resTypeKey,
                           CLUSREG_NAME_RESTYPE_IS_ALIVE,
                           &resType->IsAlivePollInterval,
                           NULL );

    if ( status != NO_ERROR ) {
        if ( status == ERROR_FILE_NOT_FOUND ) {
            resType->IsAlivePollInterval = CLUSTER_RESTYPE_DEFAULT_IS_ALIVE;
        } else {
            ClRtlLogPrint(LOG_CRITICAL,
                          "[FM] The IsAlive poll interval for the %1!ws! resource type "
                          "could not be read from the registry. Resources of this type "
                          "will not be monitored. The error was %2!d!.\n",
                          OmObjectId(resType),
                          status);
            goto error_exit;
        }
    }

    //
    // Get the optional DebugPrefix string... this is on the resource type.
    //
    dwSize = 0;
    status = DmQuerySz( resTypeKey,
                        CLUSREG_NAME_RESTYPE_DEBUG_PREFIX,
                        &resType->DebugPrefix,
                        &dwSize,
                        &stringSize );

    //
    // Get the optional DebugControlFunctions registry value
    //
    resType->Flags &= ~RESTYPE_DEBUG_CONTROL_FUNC;
    temp = 0;
    status = DmQueryDword( resTypeKey,
                           CLUSREG_NAME_RESTYPE_DEBUG_CTRLFUNC,
                           &temp,
                           NULL );

    if ( status != NO_ERROR ) {
        if ( status != ERROR_FILE_NOT_FOUND ) {
            ClRtlLogPrint(LOG_CRITICAL,
                          "[FM] The Debug control functions for the %1!ws! resource type "
                          "could not be read from the registry. Resources of this type "
                          "will not be monitored. The error was %2!d!.\n",
                          OmObjectId(resType),
                          status);
            goto error_exit;
        }
    }

    if ( temp ) {
        resType->Flags |= RESTYPE_DEBUG_CONTROL_FUNC;
    }


    //ss: bug make sure you free the old memory
    InitializeListHead(&(resType->PossibleNodeList));
    
    //
    // Get the Possible Nodes
    //
    dwSize = 0;
    status = DmQueryMultiSz( resTypeKey,
                           CLUSREG_NAME_RESTYPE_POSSIBLE_NODES,
                           &pmszPossibleNodes,
                           &dwSize,
                           &stringSize);


    if ( status != NO_ERROR ) 
    {
        //if the possible node list is not found this is ok
        if ( status != ERROR_FILE_NOT_FOUND ) 
        {
            ClRtlLogPrint(LOG_CRITICAL,
                          "[FM] The Possible nodes list for the %1!ws! resource type "
                          "could not be read from the registry. Resources of this type "
                          "will not be monitored. The error was %2!d!.\n",
                          OmObjectId(resType),
                          status);
            goto error_exit;
        }
    }

    ClRtlLogPrint(LOG_NOISE,
        "[FM] FmpQueryResTypeInfo: Calling FmpAddPossibleNodeToList for restype %1!ws!\r\n",
        OmObjectId(resType));

    status = FmpAddPossibleNodeToList(pmszPossibleNodes, stringSize, 
        &resType->PossibleNodeList);
    if ( status != ERROR_SUCCESS ) 
    {
        ClRtlLogPrint(LOG_CRITICAL,
            "[FM] FmpCreateResType: FmpAddPossibleNodeToList() failed, status=%1!u!\r\n",
            status);
        goto error_exit;
    }


error_exit:
    if (pmszPossibleNodes) LocalFree(pmszPossibleNodes);
    DmCloseKey(resTypeKey);

    return(status);

} // FmpQueryResTypeInfo



VOID
FmpGroupChangeCallback(
    IN DWORD_PTR  Context1,
    IN DWORD_PTR  Context2,
    IN DWORD      CompletionFilter,
    IN LPCWSTR    RelativeName
    )

/*++

Routine Description:

    This routine basically flushes our cached data for the given group.

Arguments:

    Context1 - A pointer to the Group object that was modified.

    Context2 - Not used.

    CompletionFilter - Not used.

    RelativeName - The registry path relative to the entry that was modified.
                   Not used.

Return Value:

    None.

--*/

{
    PFM_GROUP   Group = (PFM_GROUP)Context1;
    HDMKEY      groupKey;
    DWORD       status;
    DWORD       temp;
    BOOL        notify = FALSE;
    DWORD       dwBufferSize = 0;
    DWORD       dwStringSize;

    groupKey = Group->RegistryKey;
    if ( groupKey == NULL ) {
        return;
    }

    //
    // Re-fetch all of the data for the group.
    //
    // Name changes are managed elsewhere.
    // The Contains list is managed elsewhere.
    //

    //
    // Get the OPTIONAL PreferredOwners list.
    // *** NOTE *** This MUST be done before processing the contains list!
    //
    status = FmpQueryGroupNodes(Group, groupKey);
    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_UNUSUAL,"[FM] Error %1!d! refreshing preferred owners list\n",status);
    }

    //
    // Get the AutoFailbackType.
    //
    temp = Group->FailbackType;
    status = DmQueryDword( groupKey,
                           CLUSREG_NAME_GRP_FAILBACK_TYPE,
                           &temp,
                           &temp );

    //
    // Verify that AutoFailbackType is okay.
    //

    if ( temp >= FailbackMaximum ) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Illegal refresh value for AutoFailbackType on %1!ws!\n",
                   OmObjectId(Group));
    } else {
        if ( (UCHAR)temp != Group->FailbackType ) {
            notify = TRUE;
        }
        Group->FailbackType = (UCHAR)temp;
    }

    //
    // Get the FailbackWindowStart.
    //
    temp = Group->FailbackWindowStart;
    status = DmQueryDword( groupKey,
                           CLUSREG_NAME_GRP_FAILBACK_WIN_START,
                           &temp,
                           &temp );

    //
    // Verify that FailbackWindowStart is okay.
    //

    if ( temp > 24 ) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Illegal refresh value for FailbackWindowStart on %1!ws!\n",
                   OmObjectId(Group));
    } else {
        if ( (UCHAR)temp != Group->FailbackWindowStart ) {
            notify = TRUE;
        }
        Group->FailbackWindowStart = (UCHAR)temp;
    }

    //
    // Get the FailbackWindowEnd.
    //
    temp = Group->FailbackWindowEnd;
    status = DmQueryDword( groupKey,
                           CLUSREG_NAME_GRP_FAILBACK_WIN_END,
                           &temp,
                           &temp );

    //
    // Verify that FailbackWindowEnd is okay.
    //

    if ( temp > 24 ) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Illegal refresh value for FailbackWindowEnd on %1!ws!\n",
                   OmObjectId(Group));
    } else {
        if ( (UCHAR)temp != Group->FailbackWindowEnd ) {
            notify = TRUE;
        }
        Group->FailbackWindowEnd = (UCHAR)temp;
    }

    //
    // Get the FailoverPeriod.
    //
    temp = Group->FailoverPeriod;
    status = DmQueryDword( groupKey,
                           CLUSREG_NAME_GRP_FAILOVER_PERIOD,
                           &temp,
                           &temp );

    //
    // Verify that FailoverPeriod is okay. Take any value up to UCHAR max.
    // In theory we could take any value... but in practice we have to convert
    // this time to milliseconds (currently). That means that 1193 hours can
    // fit in a DWORD - so that is the maximum we can take. (We are limited
    // because we use GetTickCount, which returns a DWORD in milliseconds.)
    //

    if ( temp > (1193) ) {    // we dont bother Keeping it positive?
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Illegal refresh value for FailolverPeriod on %1!ws!. Max is 596\n",
                   OmObjectId(Group));
    } else {
        if ( (UCHAR)temp != Group->FailoverPeriod ) {
            notify = TRUE;
        }
        Group->FailoverPeriod = (UCHAR)temp;
    }

    //
    // Get the FailoverThreshold.
    //
    status = DmQueryDword( groupKey,
                           CLUSREG_NAME_GRP_FAILOVER_THRESHOLD,
                           &(Group->FailoverThreshold),
                           &(Group->FailoverThreshold) );

    //
    // Verify that FailoverThreshold is okay. Take any value.
    //

    //
    // Get the current persistent state of the group.
    //
    if ( Group->PersistentState == ClusterGroupOnline ) {
        temp = 1;
    } else {
        temp = 0;
    }
    status = DmQueryDword( groupKey,
                           CLUSREG_NAME_GRP_PERSISTENT_STATE,
                           &temp,
                           &temp );
    //
    // If the group state is non-zero then we go online.
    //
    // Don't bother with change notifications... they should happen elsewhere.
    //
    if ( temp ) {
        if ( ClusterGroupOnline != Group->PersistentState ) {
            //notify = TRUE;
        }
        Group->PersistentState = ClusterGroupOnline;
    } else {
        if ( ClusterGroupOffline != Group->PersistentState ) {
            //notify = TRUE;
        }
        Group->PersistentState = ClusterGroupOffline;
    }

    //
    // Get the AntiAffinityClassName property if present.
    //
    LocalFree( Group->lpszAntiAffinityClassName );
    Group->lpszAntiAffinityClassName = NULL;
    status = DmQueryMultiSz( groupKey,
                             CLUSREG_NAME_GRP_ANTI_AFFINITY_CLASS_NAME,
                             &Group->lpszAntiAffinityClassName,
                             &dwBufferSize,
                             &dwStringSize );

    //
    //  Handle the case in which the string is empty.
    //
    if ( ( status == ERROR_SUCCESS ) &&
         ( Group->lpszAntiAffinityClassName != NULL ) &&
         ( Group->lpszAntiAffinityClassName[0] == L'\0' ) )
    {
        LocalFree( Group->lpszAntiAffinityClassName );
        Group->lpszAntiAffinityClassName = NULL;
    }

    // We're done!
    if ( !FmpShutdown &&
         notify ) {
        ClusterEvent( CLUSTER_EVENT_GROUP_PROPERTY_CHANGE, Group );
    }

    return;

} // FmpGroupChangeCallback



VOID
FmpResourceChangeCallback(
    IN DWORD_PTR  Context1,
    IN DWORD_PTR  Context2,
    IN DWORD      CompletionFilter,
    IN LPCWSTR    RelativeName
    )

/*++

Routine Description:

    This routine basically flushes our cached data for the given resource.

Arguments:

    Context1 - A pointer to the resource object that was modified.

    Context2 - Not used.

    CompletionFilter - Not used.

    RelativeName - The registry path relative to the entry that was modified.
                   Not used.

Return Value:

    None.

--*/

{
    PFM_RESOURCE Resource = (PFM_RESOURCE)Context1;
    HDMKEY      resourceKey;
    DWORD       status;
    DWORD       separateMonitor;
    DWORD       zero = 0;
    DWORD       temp;
    BOOL        notify = FALSE;
    DWORD       dwDefault;

    resourceKey = Resource->RegistryKey;
    if ( resourceKey == NULL ) {
        return;
    }

    //
    // Re-fetch all of the data for the resource.
    //
    // Name changes are managed elsewhere.
    // The dependency list is managed elsewhere.
    //
    // We can't change the resource type here!
    // We can't stop the resource to start it in a separate monitor either.
    //

#if 0
    //
    // Get the Name.
    //
    status = DmQuerySz( resourceKey,
                        CLUSREG_NAME_RES_NAME,
                        &resourceName,
                        &resourceNameStringMaxSize,
                        &resourceNameStringSize );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to read name for resource %1!ws!\n",
                   OmObjectId(Resource));
        if ( OmObjectName( Resource ) == NULL ) {
            wsprintf( unknownName,
                      L"_Unknown%u",
                      InterlockedIncrement( &FmpUnknownCount ));
            status = OmSetObjectName( Resource, unknownName );
        } else {
            status = ERROR_SUCCESS;
        }
    } else {
        status = OmSetObjectName( Resource, resourceName );
    }

    if ( status != ERROR_SUCCESS ) {
        ClRtlLogPrint(LOG_ERROR,
                   "[FM] Unable to set name %1!ws! for resource %2!ws!, error %3!u!.\n",
                   resourceName,
                   OmObjectId(Resource),
                   status );
    }
#endif

    //
    // Get the IsAlive poll interval
    //
    temp = Resource->IsAlivePollInterval;
    dwDefault = CLUSTER_RESOURCE_DEFAULT_IS_ALIVE;
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_IS_ALIVE,
                           &Resource->IsAlivePollInterval,
                           &dwDefault );

    if ( status != NO_ERROR ) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to refresh IsAlivePollInterval for resource %1!ws!. Error %2!u!\n",
                   OmObjectId(Resource),
                   status);

    } else {
        CL_ASSERT( Resource->Type->IsAlivePollInterval != 0 );
        if ( temp != Resource->IsAlivePollInterval ) {
            notify = TRUE;
        }
        if ( Resource->IsAlivePollInterval == CLUSTER_RESOURCE_USE_DEFAULT_POLL_INTERVAL ) {
            Resource->IsAlivePollInterval = Resource->Type->IsAlivePollInterval;
        }
    }

    //
    // Get the LooksAlive poll interval
    //
    temp = Resource->LooksAlivePollInterval;
    dwDefault = CLUSTER_RESOURCE_DEFAULT_LOOKS_ALIVE;
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_LOOKS_ALIVE,
                           &Resource->LooksAlivePollInterval,
                           &dwDefault );

    if ( status != NO_ERROR ) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to refresh LooksAlivePollInterval for resource %1!ws!. Error %2!u!\n",
                   OmObjectId(Resource),
                   status);
    } else {
        CL_ASSERT( Resource->Type->IsAlivePollInterval != 0 );
        if ( temp != Resource->LooksAlivePollInterval ) {
            notify = TRUE;
        }
        if ( Resource->LooksAlivePollInterval == CLUSTER_RESOURCE_USE_DEFAULT_POLL_INTERVAL ) {
            Resource->LooksAlivePollInterval = Resource->Type->LooksAlivePollInterval;
        }
    }

    //
    // Get the RestartThreshold.
    //
    temp = Resource->RestartThreshold;
    dwDefault = CLUSTER_RESOURCE_DEFAULT_RESTART_THRESHOLD;
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_RESTART_THRESHOLD,
                           &Resource->RestartThreshold,
                           &dwDefault);

    // Verify the RestartThreshold. Take any value.
    if ( (status == NO_ERROR) &&
         (temp != Resource->RestartThreshold) ) {
        notify = TRUE;
    }

    //
    // Get the RestartPeriod.
    //
    temp = Resource->RestartPeriod;
    dwDefault = CLUSTER_RESOURCE_DEFAULT_RESTART_PERIOD;
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_RESTART_PERIOD,
                           &Resource->RestartPeriod,
                           &dwDefault );

    if ( (status ==  NO_ERROR) &&
         (temp != Resource->RestartPeriod) ) {
        notify = TRUE;
    }

    // Verify the RestartPeriod. Take any value.

    //
    // Get the RestartAction.
    //
    temp = Resource->RestartAction;
    dwDefault = CLUSTER_RESOURCE_DEFAULT_RESTART_ACTION;
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_RESTART_ACTION,
                           &Resource->RestartAction,
                           &dwDefault);

    // Verify the RestartAction.

    if ( status == NO_ERROR ) {
        if ( temp != Resource->RestartAction ) {
            notify = TRUE;
        }
        if ( Resource->RestartAction >= RestartMaximum ) {
            ClRtlLogPrint(LOG_NOISE,
                       "[FM] Illegal RestartAction refresh for resource %1!ws!\n",
                       OmObjectId(Resource));
        }
    }

    temp = Resource->RetryPeriodOnFailure;
    dwDefault = CLUSTER_RESOURCE_DEFAULT_RETRY_PERIOD_ON_FAILURE;
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_RETRY_PERIOD_ON_FAILURE,
                           &Resource->RetryPeriodOnFailure,
                           &dwDefault );

    // make sure that RetryPeriodOnFailure >= RestartPeriod
    if (Resource->RetryPeriodOnFailure < Resource->RestartPeriod)
    {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Specified RetryPeriodOnFailure value is less than RestartPeriod value - setting RetryPeriodOnFailure equal to RestartPeriod \n");
        Resource->RetryPeriodOnFailure = Resource->RestartPeriod;              
        
    }   
    if( temp != Resource->RetryPeriodOnFailure)
        notify = TRUE;
    
    //
    // Get the PendingTimeout value.
    //
    temp = Resource->PendingTimeout;
    dwDefault = CLUSTER_RESOURCE_DEFAULT_PENDING_TIMEOUT;
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_PENDING_TIMEOUT,
                           &Resource->PendingTimeout,
                           &dwDefault);

    // Verify the PendingTimeout. Take any value.

    if ( (status == NO_ERROR) &&
         (temp != Resource->PendingTimeout) ) {
        notify = TRUE;
    }


    //
    // Get the current persistent state of the resource.
    //
    // Don't bother with change notifications... they should happen elsewhere.
    //
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_PERSISTENT_STATE,
                           &temp,
                           NULL );

    //
    // Save the current resource state.
    //

    if ( ( status == ERROR_FILE_NOT_FOUND )  || 
       ( ( status == ERROR_SUCCESS ) && ( temp == CLUSTER_RESOURCE_DEFAULT_PERSISTENT_STATE ) ) ) {
        switch ( Resource->Group->PersistentState ) {
        case ClusterGroupOnline:
            Resource->PersistentState = ClusterResourceOnline;
            break;
        case ClusterGroupOffline:
            Resource->PersistentState = ClusterResourceOffline;
            break;
        default:
            break;
        }
    } else if ( status != NO_ERROR ) {
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Unable to read PersistentState for resource %1!ws!. Error %2!u!\n",
                   OmObjectId(Resource),
                   status);
        return;
    } else if ( temp ) {
        Resource->PersistentState = ClusterResourceOnline;
    } else {
        Resource->PersistentState = ClusterResourceOffline;
    }

#if 0  // Do this work when bringing the resource online!
    //
    // Determine the monitor to run this in. This is only updated from
    // the node that owns the resource.
    //
    status = DmQueryDword( resourceKey,
                           CLUSREG_NAME_RES_SEPARATE_MONITOR,
                           &separateMonitor,
                           &zero );
    //
    // Only do work if the flag changes.
    //
    if ( (!separateMonitor &&
         (Resource->Flags & RESOURCE_SEPARATE_MONITOR)) ||
         (separateMonitor &&
         ((Resource->Flags & RESOURCE_SEPARATE_MONITOR) == 0)) ) {

        //
        // If the resource is not offline or the quorum resource, then
        // we'll have to wait until the cluster service restarts.
        //
        //
        // The separate monitor flag has changed... tell ResMon to close
        // the resource and then create it again.
        //
        if ( (Resource->State != ClusterResourceOffline) ||
             Resource->QuorumResource ) {
            ClRtlLogPrint(LOG_NOISE,
                       "[FM] Can't change separate monitor flag on the fly... we'll pick this up on next cluster start\n",
                       OmObjectId(Resource) );
            // Now fall through to post the other changes.
        } else {
            if ( FmpPostNotification( (RM_NOTIFY_KEY)Resource,
                                      RmRestartResource,
                                      Resource->PersistentState ) ) {
                return;
            } else {
                ClRtlLogPrint(LOG_UNUSUAL,
                           "[FM] Failed to post notification to restart resource '%1!ws!'.\n",
                           OmObjectId(Resource) );
            }
        }
    }
#endif

    if ( !FmpShutdown &&
         notify ) {
        //
        // Comments from sunitas: Tell the resource monitor about the 
        // changes but do this from the worker thread. Originally, this
        // used to be a post notification to the FmpRmWorkerThread
        // which posts resmon notifications to clussvc.
        //
        OmReferenceObject(Resource);
        FmpPostWorkItem(FM_EVENT_INTERNAL_RESOURCE_CHANGE_PARAMS,
                        Resource,
                        0);
    }

#if 0   // The post notification above handles the event notification
    if ( !FmpShutdown &&
         notify ) {
        ClusterEvent( CLUSTER_EVENT_RESOURCE_PROPERTY_CHANGE, Resource );
    }
#endif

    return;

} // FmpResourceChangeCallback



DWORD
FmpChangeResourceMonitor(
    IN PFM_RESOURCE Resource,
    IN DWORD        SeparateMonitor
    )

/*++

Routine Description:

    This routine switches the resource from one resource monitor to another.

Arguments:

    Resource - pointer to the resource that was modified.

    SeparateMonitor - flag to indicate whether to run in a separate monitor;

Return Value:

    ERROR_SUCCESS if successful.
    A Win32 error code on failure.

Notes:

    The resource should be offline.

--*/

{
    DWORD       status = ERROR_SUCCESS;
    DWORD       separateMonitor;
    DWORD       zero = 0;

    if ( Resource->RegistryKey == NULL ) {
        return(ERROR_INVALID_STATE);
    }

    if ( (Resource->State != ClusterResourceOffline) &&
         (Resource->State != ClusterResourceFailed) ) {
        return(ERROR_INVALID_STATE);
    }

    //
    // Determine the monitor to run this in. This is only updated from
    // the node that owns the resource.
    //
    if ( (!SeparateMonitor &&
         (Resource->Flags & RESOURCE_SEPARATE_MONITOR)) ||
         (SeparateMonitor &&
         ((Resource->Flags & RESOURCE_SEPARATE_MONITOR) == 0)) ) {

        //
        // The separate monitor flag has changed... tell ResMon to close
        // the resource and then create it again.
        //
        ClRtlLogPrint(LOG_NOISE,
                   "[FM] Changing Separate Resource Monitor state\n");

        status = FmpRmCloseResource( Resource );
        if ( status == ERROR_SUCCESS ) {
            if ( Resource->Flags & RESOURCE_SEPARATE_MONITOR ) {
                Resource->Flags &= ~RESOURCE_SEPARATE_MONITOR;
            } else {
                Resource->Flags |= RESOURCE_SEPARATE_MONITOR;
            }
            status = FmpRmCreateResource( Resource );
            if ( status != ERROR_SUCCESS ) {
                ClRtlLogPrint(LOG_UNUSUAL,
                           "[FM] Separate resource monitor changed for '%1!ws!', but failed to re-open the resource, error %2!u!.\n",
                           OmObjectId(Resource),
                           status );
            }
        } else {
            ClRtlLogPrint(LOG_UNUSUAL,
                       "[FM] Separate resource monitor changed for '%1!ws!', but failed to close the resource, error %2!u!.\n",
                       OmObjectId(Resource),
                       status );
            return(status);
        }
    }

    return(status);

} // FmpChangeResourceMonitor