/*++ Copyright (c) 1996-1999 Microsoft Corporation Module Name: fmapi.c Abstract: Cluster manager api service routines. Author: Rod Gamache (rodga) 8-Mar-1996 Revision History: --*/ #include "fmp.h" #define LOG_MODULE FMAPI // // Local Functions // DWORD FmpCanonicalizePath( IN OUT LPWSTR lpszPath, OUT PBOOL pfIsPathUNC ); // // Functions Exported to the rest of the Cluster Manager // //////////////////////////////////////////////////////// // // Group management functions. // //////////////////////////////////////////////////////// DWORD WINAPI FmOnlineGroup( IN PFM_GROUP Group ) /*++ Routine Description: Bring the specified group online. This means bringing all of the individual resources contained within the group online. This is an atomic operation - so either all resources contained within the group are brought online, or none of them are. Arguments: Group - Supplies a pointer to the group structure to bring online. Retruns: ERROR_SUCCESS if the request was successful. A Win32 error code on failure. --*/ { DWORD status; PLIST_ENTRY listEntry; FmpMustBeOnline( ); FmpAcquireLocalGroupLock( Group ); //if the group has been marked for delete, then fail this call if (!IS_VALID_FM_GROUP(Group)) { FmpReleaseLocalGroupLock( Group); return (ERROR_GROUP_NOT_AVAILABLE); } // // Make sure the owning node is not paused. // if (NmGetNodeState(Group->OwnerNode) == ClusterNodePaused) { FmpReleaseLocalGroupLock( Group ); return(ERROR_SHARING_PAUSED); } // // Check if we are the owner... if not, ship the request off someplace // else. // if ( Group->OwnerNode != NmLocalNode ) { FmpReleaseLocalGroupLock( Group ); return(FmcOnlineGroupRequest(Group)); } // // Set the PersistentState for this Group - the PersistentState is persistent. // FmpSetGroupPersistentState( Group, ClusterGroupOnline ); status = FmpOnlineGroup( Group, TRUE ); FmpReleaseLocalGroupLock( Group ); return(status); } // FmOnlineGroup DWORD WINAPI FmOfflineGroup( IN PFM_GROUP Group ) /*++ Routine Description: Bring the specified group offline. This means bringing all of the individual resources contained within the group offline. Arguments: Group - Supplies a pointer to the group structure to bring offline. Returns: ERROR_SUCCESS if the request was successful. A Win32 error code on failure. --*/ { DWORD status = ERROR_SUCCESS; PLIST_ENTRY listEntry; PFM_RESOURCE Resource; FmpMustBeOnline( ); // // Check if we are the owner... if not, ship the request off to some // other place. // if ( Group->OwnerNode != NmLocalNode ) { return(FmcOfflineGroupRequest(Group)); } // // Set the PersistentState for this Group - the PersistentState is persistent. // FmpSetGroupPersistentState( Group, ClusterGroupOffline ); status = FmpOfflineGroup( Group, FALSE, TRUE); return(status); } // FmOfflineGroup DWORD WINAPI FmMoveGroup( IN PFM_GROUP Group, IN PNM_NODE DestinationNode OPTIONAL ) /*++ Routine Description: Failover the specified Group. This means taking all of the individual resources contained within the group offline and requesting the DestinationNode to bring the Group Online. Arguments: Group - Supplies a pointer to the group structure to move. DestinationNode - Supplies the node object to move the group to. If not present, then move it to THE OTHER node. Returns: ERROR_SUCCESS if the request was successful. A Win32 error code on failure. Notes: The Group may or may not be online on the DestinationNode, depending on whether the online request succeeded. This means that the status return is merely the status return for the Online request for the DestinationNode. --*/ { FmpMustBeOnline( ); return(FmpDoMoveGroup( Group, DestinationNode, TRUE )); } // FmMoveGroup PFM_GROUP WINAPI FmCreateGroup( IN LPWSTR GroupId, IN LPCWSTR GroupName ) /*++ Routine Description: Create the specified GroupId. This requires verifying that the specified GroupId does not already exist and then creating an empty Group container into which resources can be added. Note that the returned PFM_GROUP will have already been referenced. This prevents somebody from deleting the group before the caller gets a chance to reference it. Arguments: GroupId - Supplies the Id of the Group to create. GroupName - Supplies the 'user-friendly' name of the Group. Returns: Pointer to the newly created group if successful. NULL if unsuccessful. GetLastError() will return the specific error. --*/ { DWORD Status; PFM_GROUP Group; PGUM_CREATE_GROUP GumGroup; DWORD BufSize; DWORD GroupIdLen; DWORD GroupNameLen; FmpMustBeOnlineEx( NULL ); // // Allocate a message buffer. // GroupIdLen = (lstrlenW(GroupId)+1)*sizeof(WCHAR); GroupNameLen = (lstrlenW(GroupName)+1)*sizeof(WCHAR); BufSize = sizeof(GUM_CREATE_GROUP) - sizeof(WCHAR) + GroupIdLen + GroupNameLen + (lstrlenW( OmObjectId(NmLocalNode) ) + 1) * sizeof(WCHAR); GumGroup = LocalAlloc(LMEM_FIXED, BufSize); if (GumGroup == NULL) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmCreateGroup: Unable to alloc mem for group <%1!ws!>....\n", GroupName); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return(NULL); } // // Fill in message buffer. // GumGroup->Group = NULL; GumGroup->GroupIdLen = GroupIdLen; GumGroup->GroupNameLen = GroupNameLen; wcscpy(GumGroup->GroupId, GroupId); CopyMemory((PCHAR)GumGroup->GroupId + GroupIdLen, GroupName, GroupNameLen); CopyMemory((PCHAR)GumGroup->GroupId + GroupIdLen + GroupNameLen, OmObjectId(NmLocalNode), (lstrlenW( OmObjectId(NmLocalNode) ) + 1) * sizeof(WCHAR)); // // Send message. // Status = GumSendUpdate(GumUpdateFailoverManager, FmUpdateCreateGroup, BufSize, GumGroup); if ((GumGroup->Group == NULL) && (FmpShutdown)) { Status = ERROR_CLUSTER_NODE_SHUTTING_DOWN; } if (Status != ERROR_SUCCESS) { LocalFree(GumGroup); SetLastError(Status); return(NULL); } Group = GumGroup->Group; CL_ASSERT(Group != NULL); LocalFree(GumGroup); return(Group); } // FmCreateGroup DWORD WINAPI FmDeleteGroup( IN PFM_GROUP pGroup ) /*-- Routine Description: Delete the specified Group. This means verifying that the specified Group does not contain any resources (resources must be removed by a separate call to remove the resources), and then deleting the Group. Arguments: Group - Supplies the Group to delete. Returns: ERROR_SUCCESS if the request was successful. A Win32 error code on failure. --*/ { DWORD dwStatus; FmpMustBeOnline( ); FmpAcquireLocalGroupLock( pGroup ); if (pGroup->OwnerNode == NmLocalNode) { dwStatus = FmpDeleteGroup(pGroup); } else { // // FmcDeleteGroup releases the group lock // dwStatus = FmcDeleteGroupRequest(pGroup); goto FnExit; } FmpReleaseLocalGroupLock(pGroup); FnExit: return(dwStatus); } // FmDeleteGroup DWORD WINAPI FmSetGroupName( IN PFM_GROUP Group, IN LPCWSTR FriendlyName ) /*++ Routine Description: Set the user-friendly name for the specified Group. Note that the Group must have already been created. It is also assumed that the caller of this routine (the cluster API) has already verified that the name is NOT a duplicate. Arguments: Group - Supplies the Group to enter a new name. FriendlyName - Supplies the user-friendly name for the resource. Returns: ERROR_SUCCESS if successful. A Win32 error code on failure. --*/ { LPCWSTR GroupId; DWORD Status; GroupId = OmObjectId(Group); Status = GumSendUpdateEx(GumUpdateFailoverManager, FmUpdateChangeGroupName, 2, (lstrlenW(GroupId)+1)*sizeof(WCHAR), GroupId, (lstrlenW(FriendlyName)+1)*sizeof(WCHAR), FriendlyName); return(Status); } // FmSetGroupName CLUSTER_GROUP_STATE WINAPI FmGetGroupState( IN PFM_GROUP Group, OUT LPWSTR NodeName, IN OUT PDWORD NameLength OPTIONAL ) /*++ Routine Description: Get the current state for the specified Group. The Group state consists of state of the group, along with the current node that is hosting the Group (if the state of the Group is anything but ClusterGroupOffline. Arguments: Group - Supplies the group object to get the state. NodeName - Supplies a pointer to a buffer into which the name of the node in the cluster the specified Group is currently hosted. This field can be NULL, if NameLength is zero. NameLength - Supplies a pointer to a DWORD containing the number of characters available to the NodeName buffer (including the terminating NULL character. On return, it is the number of characters written into the NodeName buffer not including the NULL character. Returns: Returns the current state of the group: ClusterGroupOnline ClusterGroupOffline ClusterGroupPending ClusterGroupPartialOnline ClusterGroupFailed If the function fails, then the return value is ClusterGroupStateUnknown. --*/ { CLUSTER_GROUP_STATE state; DWORD nameLength=0; DWORD length; if ( ARGUMENT_PRESENT( NameLength ) ) { nameLength = *NameLength; *NodeName = (WCHAR)0; *NameLength = 0; } FmpMustBeOnlineEx( ClusterGroupStateUnknown ); FmpAcquireLocalGroupLock( Group ); //if the group has been marked for delete, then fail this call if (!IS_VALID_FM_GROUP(Group)) { FmpReleaseLocalGroupLock( Group); return (ERROR_GROUP_NOT_AVAILABLE); } // // Check if the OwnerNodes exists // // SS: dont filter out the node if it not in the preferred list // how is the poor user going to know who the current owner is?? if (Group->OwnerNode != NULL) { // // The Group is 'owned' by some system // if ( ARGUMENT_PRESENT( NameLength ) ) { length = lstrlenW( OmObjectName(Group->OwnerNode) ) + 1; if ( nameLength < length ) { length = nameLength; } lstrcpynW( NodeName, OmObjectName(Group->OwnerNode), length ); *NameLength = length; } } // // Get the group state which is not normalized // state = FmpGetGroupState( Group, FALSE ); FmpReleaseLocalGroupLock( Group ); if ( state == ClusterGroupStateUnknown ) { SetLastError(ERROR_INVALID_STATE); } return(state); } // FmGetGroupState DWORD WINAPI FmEnumerateGroupResources( IN PFM_GROUP Group, IN FM_ENUM_GROUP_RESOURCE_ROUTINE EnumerationRoutine, IN PVOID Context1, IN PVOID Context2 ) /*++ Routine Description: Enumerate all the resources in a group. Arguments: Group - Supplies the group which must be enumerated. EnumerationRoutine - The enumeration function. Context1 - The enumeration list (allocated by the caller). Context2 - Size of the enumerated list. Returns: ERROR_SUCCESS on success. A Win32 error code otherwise. Comments: This function executes only when the FM is fully online. --*/ { FmpMustBeOnline(); FmpEnumerateGroupResources( Group, EnumerationRoutine, Context1, Context2 ); return(ERROR_SUCCESS); } // FmEnumerateGroupResources DWORD FmpEnumerateGroupResources( IN PFM_GROUP pGroup, IN FM_ENUM_GROUP_RESOURCE_ROUTINE pfnEnumerationRoutine, IN PVOID pContext1, IN PVOID pContext2 ) /*++ Routine Description: Enumerate all the resources in a group. Arguments: pGroup - Supplies the group which must be enumerated. pfnEnumerationRoutine - The enumeration function. pContext1 - The enumeration list (allocated by the caller). pContext2 - Size of the enumerated list. Returns: ERROR_SUCCESS. Comments: This function executes even when the FM is not fully online. This is necessary for a joining node to query the resource states while the owner node of the group is shutting down. --*/ { PFM_RESOURCE pResource; PLIST_ENTRY pListEntry; ClRtlLogPrint(LOG_NOISE, "[FM] FmpEnumerateGroupResources: Entry for group <%1!ws!>....\n", OmObjectId(pGroup)); FmpAcquireLocalGroupLock( pGroup ); // // If the group has been marked for delete, then fail this call // if ( !IS_VALID_FM_GROUP( pGroup ) ) { ClRtlLogPrint(LOG_UNUSUAL, "[FM] FmpEnumerateGroupResources: Group <%1!ws!> marked for deletion....\n", OmObjectId(pGroup)); goto FnExit; } // // Run through contains list, then find all resources under that tree. // for ( pListEntry = pGroup->Contains.Flink; pListEntry != &(pGroup->Contains); pListEntry = pListEntry->Flink ) { pResource = CONTAINING_RECORD( pListEntry, FM_RESOURCE, ContainsLinkage ); if ( !pfnEnumerationRoutine( pContext1, pContext2, pResource, OmObjectId( pResource ) ) ) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmpEnumerateGroupResources: Enumeration routine for group <%1!ws!> fails....\n", OmObjectId(pGroup)); break; } } FnExit: FmpReleaseLocalGroupLock( pGroup ); ClRtlLogPrint(LOG_NOISE, "[FM] FmpEnumerateGroupResources: Exit for group <%1!ws!>....\n", OmObjectId(pGroup)); return( ERROR_SUCCESS ); } // FmpEnumerateGroupResources //////////////////////////////////////////////////////// // // Resource management functions. // //////////////////////////////////////////////////////// PFM_RESOURCE WINAPI FmCreateResource( IN PFM_GROUP Group, IN LPWSTR ResourceId, IN LPCWSTR ResourceName, IN LPCWSTR ResourceType, IN DWORD dwFlags ) /*++ Routine Description: Create the specified resource. Note that the returned PFM_RESOURCE will have already been referenced. This prevents somebody from deleting the resource before the caller gets a chance to reference it. Arguments: Group - Supplies the group in which this resource belongs. ResourceId - Supplies the Id of the resource to create. ResourceName - Supplies the 'user-friendly' name of the resource. ResourceType - Supplies the 'user-friendly' name of the resource type. dwFlags - The flags for the resource. Returns: Pointer to the newly created resource if successful. NULL if unsuccessful. GetLastError() will return the specific error. --*/ { DWORD Status; PFM_RESOURCE Resource; LPCWSTR GroupId; PGUM_CREATE_RESOURCE GumResource; DWORD GroupIdLen; DWORD ResourceIdLen; DWORD ResourceNameLen; DWORD ResourceTypeLen; DWORD BufSize; HDMKEY ResourceKey; HDMKEY ParamsKey; DWORD Disposition; FmpMustBeOnlineEx( NULL ); FmpAcquireLocalGroupLock( Group ); // // If we own the group then we can issue the Gum request to create // the resource. Otherwise, request the owner to initiate the request. // if ( Group->OwnerNode == NmLocalNode ) { // // Allocate a message buffer. // GroupId = OmObjectId(Group); GroupIdLen = (lstrlenW(GroupId)+1) * sizeof(WCHAR); ResourceIdLen = (lstrlenW(ResourceId)+1) * sizeof(WCHAR); ResourceNameLen = (lstrlenW(ResourceName)+1) * sizeof(WCHAR); ResourceTypeLen = (lstrlenW(ResourceType)+1) * sizeof(WCHAR); BufSize = sizeof(GUM_CREATE_RESOURCE) - sizeof(WCHAR) + GroupIdLen + ResourceIdLen + ResourceNameLen + ResourceTypeLen + 2 * sizeof( DWORD ); GumResource = LocalAlloc(LMEM_FIXED, BufSize); if (GumResource == NULL) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmCreateResource: Unable to allocate memory for resource <%1!ws!>....\n", ResourceName); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return(NULL); } // // Fill in message buffer. // GumResource->Resource = NULL; GumResource->GroupIdLen = GroupIdLen; GumResource->ResourceIdLen = ResourceIdLen; CopyMemory(GumResource->GroupId, GroupId, GroupIdLen); CopyMemory((PCHAR)GumResource->GroupId + GroupIdLen, ResourceId, ResourceIdLen); CopyMemory((PCHAR)GumResource->GroupId + GroupIdLen + ResourceIdLen, ResourceName, ResourceNameLen); CopyMemory((PCHAR)GumResource->GroupId + GroupIdLen + ResourceIdLen + ResourceNameLen, &ResourceTypeLen, sizeof( DWORD ) ); CopyMemory((PCHAR)GumResource->GroupId + GroupIdLen + ResourceIdLen + ResourceNameLen + sizeof( DWORD ), ResourceType, ResourceTypeLen ); CopyMemory((PCHAR)GumResource->GroupId + GroupIdLen + ResourceIdLen + ResourceNameLen + sizeof( DWORD ) + ResourceTypeLen, &dwFlags, sizeof( DWORD ) ); // // Send message. // Status = GumSendUpdate(GumUpdateFailoverManager, FmUpdateCreateResource, BufSize, GumResource); // // If the GUM call was successful, ensure that the resource DLL initialization stuff is // also done so that APIs following the CreateClusterResource API can make assumptions // that the resource is fully created. Note that the GUM call above will post a work item // for the FM worker thread to initialize a resource but there is no guarantee when the // FM worker thread will act on the work item. The following call will make sure we // won't return from this API until the initialization is fully done thus not giving any // chance for APIs such as ChangeClusterResourceGroup that follow this API to screw things // up. For backward compatibility reasons (consider a create call originating from a // W2K node), we still keep the work item posting in GUM and it won't do any harm since // the FmpInitializeResource call is idempotent. // if ( ( Status == ERROR_SUCCESS ) && ( GumResource->Resource != NULL ) ) { FmpClusterWideInitializeResource ( GumResource->Resource ); } FmpReleaseLocalGroupLock( Group ); if (Status != ERROR_SUCCESS) { LocalFree(GumResource); SetLastError(Status); return(NULL); } //The create resource by default adds all nodes //as possible nodes for a resource without filtering //out the nodes that dont support the resource type if( GumResource->Resource != NULL ) { FmpCleanupPossibleNodeList(GumResource->Resource); } Resource = GumResource->Resource; if( ( Resource == NULL ) && FmpShutdown ) { SetLastError( ERROR_CLUSTER_NODE_SHUTTING_DOWN ); } LocalFree(GumResource); } else { // // The Group lock is released by FmcCreateResource // Resource = FmcCreateResource( Group, ResourceId, ResourceName, ResourceType, dwFlags ); } //giving a reference to the client, increment ref count if ( Resource ) { OmReferenceObject(Resource); } return(Resource); } // FmCreateResource DWORD WINAPI FmDeleteResource( IN PFM_RESOURCE Resource ) /*++ Routine Description: Delete the specified resource. Arguments: Resource - Supplies the resource to delete. Returns: ERROR_SUCCESS if the request was successful. A Win32 error code on failure. --*/ { DWORD Status; LPCWSTR ResourceId; DWORD ResourceLen; FmpMustBeOnline( ); FmpAcquireLocalResourceLock( Resource ); // // Check if this is the quorum resource. // if ( Resource->QuorumResource ) { FmpReleaseLocalResourceLock( Resource ); return(ERROR_QUORUM_RESOURCE); } //other core resources cannot be deleted either if (Resource->ExFlags & CLUS_FLAG_CORE) { FmpReleaseLocalResourceLock( Resource ); return (ERROR_CORE_RESOURCE); } // // If we own the resource then we can issue the Gum request to delete // the resource. Otherwise, request the owner to initiate the request. // if ( Resource->Group->OwnerNode == NmLocalNode ) { // // Check the state of the resource, before attempting to delete it. // It must be offline or failed in order to perform the delete. // if ((Resource->State != ClusterResourceOffline) && (Resource->State != ClusterResourceFailed)) { FmpReleaseLocalResourceLock( Resource ); return(ERROR_RESOURCE_ONLINE); } // // Check whether this resource provides for any other resources. // If so, it cannot be deleted. // if (!IsListEmpty(&Resource->ProvidesFor)) { FmpReleaseLocalResourceLock( Resource ); return(ERROR_DEPENDENT_RESOURCE_EXISTS); } if (Resource->Group->MovingList) { FmpReleaseLocalResourceLock( Resource ); return(ERROR_INVALID_STATE); } Status = FmpBroadcastDeleteControl(Resource); if ( Status != ERROR_SUCCESS ) { FmpReleaseLocalResourceLock( Resource ); return(Status); } ResourceId = OmObjectId( Resource ); ResourceLen = (lstrlenW(ResourceId)+1) * sizeof(WCHAR); // // Send message. // Status = GumSendUpdateEx(GumUpdateFailoverManager, FmUpdateDeleteResource, 1, ResourceLen, ResourceId); FmpReleaseLocalResourceLock( Resource ); } else { Status = FmcDeleteResource( Resource ); } return(Status); } // FmDeleteResource DWORD WINAPI FmSetResourceName( IN PFM_RESOURCE Resource, IN LPCWSTR FriendlyName ) /*++ Routine Description: Set the user-friendly name for the specified resource. Note that the resource must have already been created. It is also assumed that the caller of this routine (the cluster API) has already verified that the name is NOT a duplicate. Arguments: Resource - Supplies the resource to enter a new name. FriendlyName - Supplies the user-friendly name for the resource. Returns: ERROR_SUCCESS if successful. A Win32 error code on failure. --*/ { DWORD dwStatus = ERROR_SUCCESS; dwStatus = FmpSetResourceName( Resource, FriendlyName ); if( dwStatus != ERROR_SUCCESS ) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmSetResourceName: FmpSetResourceName for resource %1!ws! fails, Status = %2!d!...\n", OmObjectId(Resource), dwStatus); } return( dwStatus ); } // FmSetResourceName DWORD WINAPI FmOnlineResource( IN PFM_RESOURCE Resource ) /*++ Routine Description: This routine brings a resource online. It also updates the registry to indicate the new persistent, desired state of the resource. Arguments: Resource - A pointer to the resource to bring online. Returns: ERROR_SUCCESS if the request is successful. ERROR_IO_PENDING if the request is pending. A Win32 error code if the request fails. --*/ { DWORD status; FmpMustBeOnline( ); FmpAcquireLocalResourceLock( Resource ); //if the resource has been marked for delete, then dont let //it be brought online if (!IS_VALID_FM_RESOURCE(Resource)) { FmpReleaseLocalResourceLock( Resource ); return (ERROR_RESOURCE_NOT_AVAILABLE); } // // Check if we are the owner... if not, ship the request off someplace // else. // CL_ASSERT( Resource->Group != NULL ); if ( Resource->Group->OwnerNode != NmLocalNode ) { FmpReleaseLocalResourceLock( Resource ); status = FmcOnlineResourceRequest( Resource ); return(status); } // // Check if the resource has been initialized. If not, attempt // to initialize the resource now. // if ( Resource->Monitor == NULL ) { status = FmpInitializeResource( Resource, TRUE ); if ( status != ERROR_SUCCESS ) { FmpReleaseLocalResourceLock( Resource ); return(status); } } // // Chittur Subbaraman (chitturs) - 08/04/2000 // // If the group is moving, fail this operation. // if ( Resource->Group->MovingList != NULL ) { FmpReleaseLocalResourceLock( Resource ); return (ERROR_GROUP_NOT_AVAILABLE); } // // Try to bring the resource online. // status = FmpDoOnlineResource( Resource, TRUE ); FmpReleaseLocalResourceLock( Resource ); return(status); } // FmOnlineResource DWORD WINAPI FmOfflineResource( IN PFM_RESOURCE Resource ) /*++ Routine Description: This routine takes a resource offline. It also updates the registry to indicate the new persistent, desired state of the resource. Arguments: Resource - A pointer to the resource to take offline. Returns: ERROR_SUCCESS if the request is successful. ERROR_IO_PENDING if the request is pending. A Win32 error code if the request fails. --*/ { DWORD status; FmpMustBeOnline( ); FmpAcquireLocalResourceLock( Resource ); //if the resource has been marked for delete, then fail this call if (!IS_VALID_FM_RESOURCE(Resource)) { FmpReleaseLocalResourceLock( Resource ); return (ERROR_RESOURCE_NOT_AVAILABLE); } // // Check if this is the quorum resource. // if ( Resource->QuorumResource ) { FmpReleaseLocalResourceLock( Resource ); return(ERROR_QUORUM_RESOURCE); } // // Chittur Subbaraman (chitturs) - 4/8/99 // // Don't attempt to do anything if the resource has failed. You could // get into some funny cases in which the resource switches between // offline pending and failed states for ever. // if ( Resource->State == ClusterResourceFailed ) { FmpReleaseLocalResourceLock( Resource ); return(ERROR_INVALID_STATE); } // // Check if we are the owner... if not, ship the request off someplace // else. // CL_ASSERT( Resource->Group != NULL ); if ( Resource->Group->OwnerNode != NmLocalNode ) { FmpReleaseLocalResourceLock( Resource ); return(FmcOfflineResourceRequest(Resource)); } // // Check if the resource has been initialized. If not, return // success because the resource is not online. // if ( Resource->Monitor == NULL ) { FmpReleaseLocalResourceLock( Resource ); return(ERROR_SUCCESS); } // // Chittur Subbaraman (chitturs) - 08/04/2000 // // If the group is moving, fail this operation. // if ( Resource->Group->MovingList != NULL ) { FmpReleaseLocalResourceLock( Resource ); return (ERROR_GROUP_NOT_AVAILABLE); } // // Take the resource offline. // FmpReleaseLocalResourceLock( Resource ); return(FmpDoOfflineResource( Resource, TRUE)); } // FmOfflineResource CLUSTER_RESOURCE_STATE WINAPI FmGetResourceState( IN PFM_RESOURCE Resource, OUT LPWSTR NodeName, IN OUT PDWORD NameLength OPTIONAL ) /*++ Routine Description: Get the current state for the specified resource. The resource state consists of state of the resource, along with the current node that is hosting the resource. Arguments: Resource - Supplies the resource object to get the state. NodeName - Supplies a pointer to a buffer into which the name of the node in the cluster the specified resource is currently hosted. This field can be NULL, if NameLength is zero. NameLength - Supplies a pointer to a DWORD containing the number of characters available to the NodeName buffer (including the terminating NULL character. On return, it is the number of characters written into the NodeName buffer not including the NULL character. Returns: Returns the current state of the resource: ClusterResourceOnline ClusterResourceOffline ClusterResourceFailed etc. If the function fails, then the return value is ClusterResourceStateUnknown. --*/ { WCHAR computerName[MAX_COMPUTERNAME_LENGTH+1]; DWORD nameLength; DWORD length; PNM_NODE OwnerNode; CLUSTER_RESOURCE_STATE state; BOOL acquired; CL_ASSERT( OmObjectSignature(Resource) == FMP_RESOURCE_SIGNATURE ); if ( ARGUMENT_PRESENT( NameLength ) ) { nameLength = *NameLength; *NodeName = (WCHAR)0; *NameLength = 0; } FmpMustBeOnlineEx( ClusterResourceStateUnknown ); // // Try to acquire the lock to perform this work, so that resources // can query their current status and where the resource should be run. // // This does leave a potential window though if we can't get the lock, // some other thread could be changing the data! // FmpTryAcquireLocalResourceLock( Resource, acquired ); OwnerNode = Resource->Group->OwnerNode; if ( OwnerNode != NULL ) { // // The Group is 'owned' by some system // if ( ARGUMENT_PRESENT( NameLength ) ) { length = lstrlenW( OmObjectName(OwnerNode) ) + 1; if ( nameLength < length ) { length = nameLength; } lstrcpynW( NodeName, OmObjectName(OwnerNode), length ); *NameLength = length; } } state = Resource->State; if ( acquired ) { FmpReleaseLocalResourceLock( Resource ); } if ( state == ClusterGroupStateUnknown ) { SetLastError(ERROR_INVALID_STATE); } return(state); } // FmGetResourceState DWORD WINAPI FmAddResourceDependency( IN PFM_RESOURCE pResource, IN PFM_RESOURCE pDependentResource ) /*++ Routine Description: Add a dependency from one resource to another. Arguments: Resource - The resource to add the dependent resource. DependentResource - The dependent resource. Returns: ERROR_SUCCESS if successful. A Win32 error code on failure. --*/ { LPCWSTR pszResourceId; DWORD dwResourceLen; LPCWSTR pszDependsOnId; DWORD dwDependsOnLen; DWORD dwStatus = ERROR_SUCCESS; // // Chittur Subbaraman (chitturs) - 5/16/99 // // Modify this API to route requests to owner node. Handle the // mixed mode case as well. // FmpMustBeOnline( ); ClRtlLogPrint(LOG_NOISE, "[FM] FmAddResourceDependency : Resource <%1!ws!>, DependentResource <%2!ws!>...\n", OmObjectId( pResource ), OmObjectId( pDependentResource )); FmpAcquireLocalResourceLock( pResource ); // // Check if we are the owner... if not, ship the request off some place // else. // if ( pResource->Group->OwnerNode != NmLocalNode ) { // // FmcAddResourceDependency releases the local resource lock // dwStatus = FmcAddResourceDependency( pResource, pDependentResource ); goto FnExit; } dwStatus = FmpValAddResourceDependency( pResource, pDependentResource ); if ( dwStatus != ERROR_SUCCESS ) { goto FnUnlock; } pszResourceId = OmObjectId( pResource ); dwResourceLen = ( lstrlenW( pszResourceId ) +1 ) * sizeof( WCHAR) ; pszDependsOnId = OmObjectId( pDependentResource ); dwDependsOnLen = ( lstrlenW( pszDependsOnId ) + 1 ) * sizeof( WCHAR ); dwStatus = GumSendUpdateEx( GumUpdateFailoverManager, FmUpdateAddDependency, 2, dwResourceLen, pszResourceId, dwDependsOnLen, pszDependsOnId ); if ( dwStatus == ERROR_SUCCESS ) { FmpBroadcastDependencyChange( pResource, pszDependsOnId, FALSE ); } FnUnlock: FmpReleaseLocalResourceLock( pResource ); FnExit: ClRtlLogPrint(LOG_NOISE, "[FM] FmAddResourceDependency Exit: Status = <%1!u!>...\n", dwStatus); return( dwStatus ); } // FmAddResourceDependency DWORD WINAPI FmRemoveResourceDependency( IN PFM_RESOURCE pResource, IN PFM_RESOURCE pDependentResource ) /*++ Routine Description: Remove a dependency from a resource. Arguments: Resource - The resource to remove the dependent resource. DependentResource - The dependent resource. Returns: ERROR_SUCCESS if successful. A Win32 error code on failure. --*/ { LPCWSTR pszResourceId; DWORD dwResourceLen; LPCWSTR pszDependsOnId; DWORD dwDependsOnLen; DWORD dwStatus; // // Chittur Subbaraman (chitturs) - 5/16/99 // // Modify this API to route requests to owner node. Handle the // mixed mode case as well. // FmpMustBeOnline( ); ClRtlLogPrint(LOG_NOISE, "[FM] FmRemoveResourceDependency : Resource <%1!ws!>, DependentResource <%2!ws!>...\n", OmObjectId( pResource ), OmObjectId( pDependentResource )); FmpAcquireLocalResourceLock( pResource ); // // Check if we are the owner... if not, ship the request off some place // else. // if ( pResource->Group->OwnerNode != NmLocalNode ) { // // FmcRemoveResourceDependency releases the local resource lock // dwStatus = FmcRemoveResourceDependency( pResource, pDependentResource ); goto FnExit; } dwStatus = FmpValRemoveResourceDependency( pResource, pDependentResource ); if ( dwStatus != ERROR_SUCCESS ) { ClRtlLogPrint(LOG_NOISE, "[FM] FmRemoveResourceDependency: FmpValRemoveResourceDependency returns status = <%1!u!>...\n", dwStatus); goto FnUnlock; } pszResourceId = OmObjectId( pResource ); dwResourceLen = ( lstrlenW( pszResourceId ) + 1 ) * sizeof( WCHAR ); pszDependsOnId = OmObjectId( pDependentResource ); dwDependsOnLen = ( lstrlenW( pszDependsOnId ) + 1 ) * sizeof( WCHAR ); dwStatus = GumSendUpdateEx( GumUpdateFailoverManager, FmUpdateRemoveDependency, 2, dwResourceLen, pszResourceId, dwDependsOnLen, pszDependsOnId ); if ( dwStatus == ERROR_SUCCESS ) { FmpBroadcastDependencyChange( pResource, pszDependsOnId, TRUE ); } FnUnlock: FmpReleaseLocalResourceLock( pResource ); FnExit: ClRtlLogPrint(LOG_NOISE, "[FM] FmRemoveResourceDependency Exit: Status = <%1!u!>...\n", dwStatus); return( dwStatus ); } // FmRemoveResourceDependency DWORD WINAPI FmEnumResourceDependent( IN PFM_RESOURCE Resource, IN DWORD Index, OUT PFM_RESOURCE *DependentResource ) /*++ Routine Description: Enumerate the dependencies of a resources. Arguments: Resource - The resource to enumerate. Index - The index for this enumeration. DependentResource - The dependent resource. The returned resource pointer will be referenced by this routine and should be dereferenced when the caller is done with it. Returns: ERROR_SUCCESS if successful. A Win32 error code on failure. --*/ { PLIST_ENTRY ListEntry; DWORD i = 0; PFM_RESOURCE Current; PDEPENDENCY Dependency; DWORD Status = ERROR_NO_MORE_ITEMS; FmpMustBeOnline( ); FmpAcquireResourceLock(); if (!IS_VALID_FM_RESOURCE(Resource)) { Status = ERROR_RESOURCE_NOT_AVAILABLE; goto FnExit; } ListEntry = Resource->DependsOn.Flink; while (ListEntry != &Resource->DependsOn) { Dependency = CONTAINING_RECORD(ListEntry, DEPENDENCY, DependentLinkage); CL_ASSERT(Dependency->DependentResource == Resource); CL_ASSERT(Dependency->ProviderResource != Resource); if (i==Index) { // // Got the right index // OmReferenceObject(Dependency->ProviderResource); *DependentResource = Dependency->ProviderResource; Status = ERROR_SUCCESS; break; } ListEntry = ListEntry->Flink; ++i; } FnExit: FmpReleaseResourceLock(); return(Status); } // FmEnumResourceDependent DWORD WINAPI FmEnumResourceProvider( IN PFM_RESOURCE Resource, IN DWORD Index, OUT PFM_RESOURCE *DependentResource ) /*++ Routine Description: Enumerate the providers for a resources. Arguments: Resource - The resource to enumerate. Index - The index for this enumeration. DependentResource - The provider resource. The returned resource pointer will be referenced by this routine and should be dereferenced when the caller is done with it. Returns: ERROR_SUCCESS if successful. A Win32 error code on failure. --*/ { PLIST_ENTRY ListEntry; DWORD i = 0; PFM_RESOURCE Current; PDEPENDENCY Dependency; DWORD Status = ERROR_NO_MORE_ITEMS; FmpMustBeOnline( ); FmpAcquireResourceLock(); if (!IS_VALID_FM_RESOURCE(Resource)) { Status = ERROR_RESOURCE_NOT_AVAILABLE; goto FnExit; } ListEntry = Resource->ProvidesFor.Flink; while (ListEntry != &Resource->ProvidesFor) { Dependency = CONTAINING_RECORD(ListEntry, DEPENDENCY, ProviderLinkage); CL_ASSERT(Dependency->DependentResource != Resource); CL_ASSERT(Dependency->ProviderResource == Resource); if (i==Index) { // // Got the right index // OmReferenceObject(Dependency->DependentResource); *DependentResource = Dependency->DependentResource; Status = ERROR_SUCCESS; break; } ListEntry = ListEntry->Flink; ++i; } FnExit: FmpReleaseResourceLock(); return(Status); } // FmEnumResourceProvider DWORD WINAPI FmEnumResourceNode( IN PFM_RESOURCE Resource, IN DWORD Index, OUT PNM_NODE *PossibleNode ) /*++ Routine Description: Enumerate the possible nodes for a resources. Arguments: Resource - The resource to enumerate. Index - The index for this enumeration. PossibleNode - The possible node. The returned node pointer will be referenced by this routine and should be dereferenced when the caller is done with it. Returns: ERROR_SUCCESS if successful. A Win32 error code on failure. --*/ { PLIST_ENTRY ListEntry; DWORD i = 0; PFM_RESOURCE Current; PPOSSIBLE_ENTRY PossibleEntry; DWORD Status = ERROR_NO_MORE_ITEMS; FmpMustBeOnline( ); FmpAcquireResourceLock(); if (!IS_VALID_FM_RESOURCE(Resource)) { Status = ERROR_RESOURCE_NOT_AVAILABLE; goto FnExit; } ListEntry = Resource->PossibleOwners.Flink; while (ListEntry != &Resource->PossibleOwners) { PossibleEntry = CONTAINING_RECORD(ListEntry, POSSIBLE_ENTRY, PossibleLinkage); if (i==Index) { // // Got the right index // OmReferenceObject(PossibleEntry->PossibleNode); *PossibleNode = PossibleEntry->PossibleNode; Status = ERROR_SUCCESS; break; } ListEntry = ListEntry->Flink; ++i; } FnExit: FmpReleaseResourceLock(); return(Status); } // FmEnumResourceNode DWORD WINAPI FmFailResource( IN PFM_RESOURCE Resource ) /*++ Routine Description: Cause the specified resource to fail. Arguments: Resource - The resource to make fail. Returns: ERROR_SUCCESS - if successful. A Win32 error code on failure. --*/ { FmpMustBeOnline( ); if ( Resource->Group->OwnerNode != NmLocalNode ) { return(FmcFailResource( Resource )); } return(FmpRmFailResource( Resource )); } // FmFailResource DWORD WINAPI FmChangeResourceNode( IN PFM_RESOURCE Resource, IN PNM_NODE Node, IN BOOL Add ) /*++ Routine Description: Changes the list of nodes where the specified resource can be brought online. Arguments: Resource - Supplies the resource whose list of possible nodes is to be modified. Node - Supplies the node to be added to the resource's list. Add - Supplies whether the specified node is to be added (TRUE) or deleted (FALSE) from the resource's node list. Return Value: ERROR_SUCCESS if successful Win32 error code otherwise --*/ { DWORD Status; FmpAcquireLocalResourceLock( Resource ); if ( Resource->Group->OwnerNode != NmLocalNode ) { // Note: FmcChangeResourceNode must release the resource lock. Status = FmcChangeResourceNode( Resource, Node, Add ); } else { Status = FmpChangeResourceNode(Resource, OmObjectId(Node), Add); FmpReleaseLocalResourceLock( Resource ); } return(Status); } // FmChangeResourceNode DWORD WINAPI FmSetQuorumResource( IN PFM_RESOURCE Resource, IN LPCWSTR pszClusFileRootPath, IN DWORD dwMaxQuorumLogSize ) /*++ Routine Description: Set the specified resource as the quorum resource. This requires making sure that the specified resource can perform an arbitrate. We do this by asking the owner node to perform an arbitrate of the resource. Arguments: Resource - Supplies the resource that must be arbitrated. pszLogPathName - The root path where the log files will be moved. "Microsoft Cluster Manager Directory" is created under the root path provided. If NULL, a partition on the shared quorum device is picked up randomly. And the log files are placed in the directory specified by the CLUSTER_QUORUM_DEFAULT_MAX_LOG_SIZE constant at the root of that partition. dwMaxQuorumLogSize - The maximum size of the quorum logs. If 0, the default used. If smaller that 32K, 32K is used. Returns: ERROR_SUCCESS if successful. A Win32 error code on failure. --*/ { DWORD status; DWORD resourceIdLen; PFM_RESOURCE quorumResource = NULL; PFM_RESOURCE pOldQuoResource = NULL; PVOID gumResource = NULL; DWORD dwBytesReturned; DWORD dwRequired; DWORD dwBufSize; WCHAR szQuoLogPath[MAX_PATH] = L"\0"; WCHAR szLogRootPath[MAX_PATH]; CLUS_RESOURCE_CLASS_INFO resClassInfo; PUCHAR pBuf = NULL; LPWSTR pszOldQuoLogPath = NULL; LPWSTR pszNext = NULL; LPWSTR pszExpClusFileRootPath = NULL; DWORD dwCharacteristics; BOOL fIsPathUNC; DWORD dwCurrentNodeCnt; DWORD dwClusterHighestVersion; FmpMustBeOnline( ); ClRtlLogPrint(LOG_NOISE, "[FM] FmSetQuorumResource: Entry, pszClusFileRootPath=%1!ws!\r\n", ((pszClusFileRootPath)? pszClusFileRootPath:szQuoLogPath)); dwCurrentNodeCnt = NmGetCurrentNumberOfNodes(); // find the old quorum resource status = FmFindQuorumResource(&pOldQuoResource); if (status != ERROR_SUCCESS) { goto FnExit; } // // Synchronize access to Quorum Resource changes. // // // Synchronize both the old and the new resource. // Lock the lowest by lowest Group Id first - to prevent deadlocks! // Note - the order of release is unimportant. // // if the old and new resource belong to the same group // the comparison will be be equal! // ACQUIRE_EXCLUSIVE_LOCK(gQuoChangeLock); if ( lstrcmpiW( OmObjectId( pOldQuoResource->Group ), OmObjectId( Resource->Group ) ) <= 0 ) { FmpAcquireLocalGroupLock( pOldQuoResource->Group ); FmpAcquireLocalGroupLock( Resource->Group ); } else { FmpAcquireLocalGroupLock( Resource->Group ); FmpAcquireLocalGroupLock( pOldQuoResource->Group ); } status = FmpGetResourceCharacteristics(Resource, &dwCharacteristics); if (status != ERROR_SUCCESS) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmSetQuorumResource: getresourcecharacteristics failed, status=%1!u!\n", status); goto FnExit; } // // If the resource doesn't advertise itself as quorum-capable, no point in proceeding. // if ( !( dwCharacteristics & CLUS_CHAR_QUORUM ) ) { status = ERROR_NOT_QUORUM_CAPABLE; ClRtlLogPrint(LOG_CRITICAL, "[FM] FmSetQuorumResource: Resource %1!ws! can't be quorum, status %2!u!\n", OmObjectName(Resource), status); goto FnExit; } if (dwCurrentNodeCnt > 1) { //if the cluster is a multinode cluster, dont allow a switch to //local quorum type resources unless the debug characteristic is set if ((dwCharacteristics & CLUS_CHAR_LOCAL_QUORUM) && !(dwCharacteristics & CLUS_CHAR_LOCAL_QUORUM_DEBUG)) { status = ERROR_INVALID_PARAMETER; ClRtlLogPrint(LOG_CRITICAL, "[FM] FmSetQuorumResource: No debug property for local quorum resource %1!ws!, status %2!u!\n", OmObjectName(Resource), status); goto FnExit; } } if (Resource->State != ClusterResourceOnline) { status = ERROR_RESOURCE_NOT_ONLINE; goto FnExit; } if (!IsListEmpty(&Resource->DependsOn)) { status = ERROR_DEPENDENCY_NOT_ALLOWED; goto FnExit; } // // Get the old log path. // dwBytesReturned = 0; dwRequired = 0; status = DmQuerySz( DmQuorumKey, cszPath, (LPWSTR*)&pszOldQuoLogPath, &dwRequired, &dwBytesReturned); if (status != ERROR_SUCCESS) { ClRtlLogPrint(LOG_UNUSUAL, "[FM] FmSetQuorumResource Failed to get the old quo log path, error %1!u!.\n", status); goto FnExit; } //SS: if you want to have a sub dir for logging files //check the resource class status = FmResourceControl(Resource, NULL, CLUSCTL_RESOURCE_GET_CLASS_INFO, NULL, 0, (PUCHAR)&resClassInfo, sizeof(resClassInfo), &dwBytesReturned, &dwRequired); if ( status != ERROR_SUCCESS ) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmSetQuorumResource: Unable to get class info for resource %1!ws!, status %2!u!\n", OmObjectName(Resource), status); goto FnExit; } if ( ( resClassInfo.SubClass & CLUS_RESSUBCLASS_SHARED ) == 0 ) { status = ERROR_NOT_QUORUM_CLASS; goto FnExit; } //allocate info for the disk info //get disk info dwBufSize = 2048; Retry: pBuf = LocalAlloc(LMEM_FIXED, dwBufSize); if (pBuf == NULL ) { status = ERROR_NOT_ENOUGH_MEMORY; goto FnExit; } status = FmResourceControl(Resource, NULL, CLUSCTL_RESOURCE_STORAGE_GET_DISK_INFO, NULL, 0, pBuf, dwBufSize, &dwBytesReturned, &dwRequired); if ((status == ERROR_MORE_DATA) && (dwBufSize < dwRequired)) { dwBufSize = dwRequired; LocalFree(pBuf); goto Retry; } if (status != ERROR_SUCCESS) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmSetQuorumResource: Unable to get disk info for resource %1!ws!, status %2!u!\n", OmObjectName(Resource), status); goto FnExit; } if (pszClusFileRootPath) pszExpClusFileRootPath = ClRtlExpandEnvironmentStrings(pszClusFileRootPath); //use the expanded path name for validation if (pszExpClusFileRootPath) { WCHAR cColon=L':'; // // The quorum management code assumes that the quorum path + file names such as quolog.log can all fit in MAX_PATH // sized buffers. It is too much code churn to fix all places in which this assumption is made. Let us reject paths that // are too long right here. // if ( lstrlen ( pszExpClusFileRootPath ) + 20 > MAX_PATH ) { status = ERROR_BAD_PATHNAME; ClRtlLogPrint(LOG_UNUSUAL, "[FM] FmSetQuorumResource: Path specified is too long, status %1!u!\n", status); goto FnExit; } pszNext = wcschr(pszExpClusFileRootPath, cColon); //pick up just the drive letter if (pszNext) { lstrcpynW(szLogRootPath, pszExpClusFileRootPath, (UINT)(pszNext-pszExpClusFileRootPath+2)); } else { //if there is no drive letter, pick up a drive letter at random szLogRootPath[0] = L'\0'; } } else { szLogRootPath[0] = L'\0'; } ClRtlLogPrint(LOG_NOISE, "[FM] FmSetQuorumResource: szLogRootPath=%1!ws!\r\n", szLogRootPath); //save the drive letter for the new quorum path status = FmpGetDiskInfoParseProperties(pBuf, dwBytesReturned, szLogRootPath); //if the status was invalid parameter for a local quorum, ignore the local //quorum path setting..what is specified through this api overrides if ((status == ERROR_INVALID_PARAMETER) && (dwCharacteristics & CLUS_CHAR_LOCAL_QUORUM)) { status = ERROR_SUCCESS; ClRtlLogPrint(LOG_NOISE, "[FM] FmSetQuorumResource: LocalQuorum force success, szLogRootPath=%1!ws!\r\n", szLogRootPath); } ClRtlLogPrint(LOG_NOISE, "[FM] FmSetQuorumResource: szLogRootPath=%1!ws!\r\n", szLogRootPath); if (status != ERROR_SUCCESS) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmSetQuorumResource: Unable to parse disk info for resource %1!ws!, status %2!u!\n", OmObjectName(Resource), status); goto FnExit; } if (szLogRootPath[0] == L'\0') { //no valid drive letter is found status = ERROR_INVALID_PARAMETER; ClRtlLogPrint(LOG_CRITICAL, "[FM] FmSetQuorumResource: No valid drive letter for resource %1!ws!, status %2!u!\n", OmObjectName(Resource), status); goto FnExit; } //got the drive letter lstrcpyW(szQuoLogPath, szLogRootPath); if (pszNext) { // if the driver letter was supplied, append the rest of the path lstrcatW(szQuoLogPath, pszNext+1); } else { //if no drive letter was supplied // if a path was supplied, append the path if ( pszExpClusFileRootPath ) { // // If the user specifies an SMB path, we override whatever the resource told us. // if ( ( lstrlenW( pszExpClusFileRootPath ) >=2 ) && ( pszExpClusFileRootPath[0] == L'\\' ) && ( pszExpClusFileRootPath[1] == L'\\' ) && ( dwCharacteristics & CLUS_CHAR_QUORUM ) ) { lstrcpyW( szQuoLogPath, pszExpClusFileRootPath ); } else if ( pszExpClusFileRootPath[0] == L'\\' ) { lstrcatW( szQuoLogPath, pszExpClusFileRootPath ); } else { lstrcatW( szQuoLogPath, L"\\" ); lstrcatW( szQuoLogPath, pszExpClusFileRootPath ); } } else { // else append the default path lstrcatW( szQuoLogPath, L"\\" ); lstrcatW(szQuoLogPath, CLUS_NAME_DEFAULT_FILESPATH); } } //if the path name is provided, check if it is terminated with '\' //if not, terminate it if (szQuoLogPath[lstrlenW(szQuoLogPath) - 1] != L'\\') { lstrcatW( szQuoLogPath, L"\\" ); } // // If the path is of UNC form, then prepend a \\?\UNC\ to it. // status = FmpCanonicalizePath( szQuoLogPath, &fIsPathUNC ); if ( status != ERROR_SUCCESS ) goto FnExit; ClRtlLogPrint(LOG_NOISE, "[FM] FmSetQuorumResource: szQuoLogPath=%1!ws!\r\n", szQuoLogPath); // // Allocate a message buffer. // resourceIdLen = (lstrlenW(OmObjectId(Resource))+1) * sizeof(WCHAR); gumResource = LocalAlloc(LMEM_FIXED, resourceIdLen); if (gumResource == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; goto FnExit; } // // Fill in message buffer. // CopyMemory(gumResource, OmObjectId(Resource), resourceIdLen); // // Make sure that we can arbitrate the new quorum resource. // if ( Resource->Group->OwnerNode != NmLocalNode ) { status = FmcArbitrateResource( Resource ); } else { status = FmpRmArbitrateResource( Resource ); } if ( status != ERROR_SUCCESS ) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmSetQuorumResource: Arbitrate for resource %1!ws! failed, status %2!u!\n", OmObjectName(Resource), status); goto FnExit; } //check the log size, if it not zero but less than the min //limit set it to 32K. if ((dwMaxQuorumLogSize) && (dwMaxQuorumLogSize < CLUSTER_QUORUM_MIN_LOG_SIZE)) { dwMaxQuorumLogSize = CLUSTER_QUORUM_MIN_LOG_SIZE; } //Prepare to move to a new quorum resource //create a new quorum log file and //move the registry files there. if ( Resource->Group->OwnerNode != NmLocalNode ) { status = FmcPrepareQuorumResChange( Resource, szQuoLogPath, dwMaxQuorumLogSize ); } else { status = FmpPrepareQuorumResChange( Resource, szQuoLogPath, dwMaxQuorumLogSize ); } if ( status != ERROR_SUCCESS ) { if ((dwCharacteristics & CLUS_CHAR_LOCAL_QUORUM) && (fIsPathUNC == FALSE)) { ClRtlLogPrint(LOG_NOISE, "[FM] FmSetQuorumResource: Local quorum, map FmpPrepareQuorumResChange to success\n"); status = ERROR_SUCCESS; } else goto FnExit; } // // If we are dealing with the mixed mode cluster then dont set the arbitration // timeouts // NmGetClusterOperationalVersion( &dwClusterHighestVersion, NULL, NULL ); if ((CLUSTER_GET_MAJOR_VERSION(dwClusterHighestVersion) < NT51_MAJOR_VERSION )) { // // This is a mixed mode cluster, send the old gum message. // status = GumSendUpdateEx(GumUpdateFailoverManager, FmUpdateChangeQuorumResource, 3, resourceIdLen, gumResource, (lstrlenW(szQuoLogPath) + 1 ) * sizeof(WCHAR), szQuoLogPath, sizeof(DWORD), &dwMaxQuorumLogSize ); } else { DWORD dwArbTimeout; CLUSPROP_DWORD ClusPropArbTimeout; // Read and set the new arbitration timeout status = FmResourceTypeControl(OmObjectId(Resource->Type), NULL, CLUSCTL_RESOURCE_TYPE_GET_ARB_TIMEOUT, NULL, 0, (CHAR *)&ClusPropArbTimeout, sizeof(ClusPropArbTimeout), &dwBytesReturned, &dwRequired); if ( status != ERROR_SUCCESS ) { ClRtlLogPrint(LOG_NOISE, "[FM] FmSetQuorumResource: couldnt get RESTYPE arbitration timeout, defaulting to 60 seconds. Status = %1!u!\n", status); dwArbTimeout = CLUSTER_QUORUM_DEFAULT_ARBITRATION_TIMEOUT; //map to success, since we dont want to fail the request in this case status = ERROR_SUCCESS; } else { dwArbTimeout = ClusPropArbTimeout.dw; } ClRtlLogPrint(LOG_NOISE, "[FM] FmSetQuorumResource: setting arbitration timeout to %1!u! seconds.\n", dwArbTimeout ); // // Send the message. // status = GumSendUpdateEx(GumUpdateFailoverManager, FmUpdateChangeQuorumResource2, 5, resourceIdLen, gumResource, (lstrlenW(szQuoLogPath) + 1 ) * sizeof(WCHAR), szQuoLogPath, sizeof(DWORD), &dwMaxQuorumLogSize, sizeof(DWORD), &dwArbTimeout, sizeof(DWORD), &dwCharacteristics ); } //if the old path is not the same as the new path //create a tombstone for the quorum log files on the old path //this is to prevent nodes that are not present in this update //from doing a form. if ( (status == ERROR_SUCCESS) && (lstrcmpiW(szQuoLogPath, pszOldQuoLogPath)) ) { // // delete the old quorum log files on the old resource and create a tombstone file // in there. // if ( pOldQuoResource->Group->OwnerNode != NmLocalNode ) { status = FmcCompleteQuorumResChange( pOldQuoResource, pszOldQuoLogPath ); } else { status = FmpCompleteQuorumResChange( OmObjectId(pOldQuoResource), pszOldQuoLogPath ); } } FnExit: //not the order of release is not important FmpReleaseLocalGroupLock(pOldQuoResource->Group); FmpReleaseLocalGroupLock(Resource->Group); RELEASE_LOCK(gQuoChangeLock); if (pBuf) LocalFree(pBuf); if (gumResource) LocalFree(gumResource); if (pOldQuoResource) OmDereferenceObject(pOldQuoResource); if (pszOldQuoLogPath) LocalFree(pszOldQuoLogPath); if (pszExpClusFileRootPath) LocalFree(pszExpClusFileRootPath); ClRtlLogPrint(LOG_NOISE, "[FM] FmSetQuorumResource: Exit, status=%1!u!\r\n", status); return(status); } // FmSetQuorumResource DWORD FmCreateResourceType( IN LPCWSTR lpszTypeName, IN LPCWSTR lpszDisplayName, IN LPCWSTR lpszDllName, IN DWORD dwLooksAlive, IN DWORD dwIsAlive ) /*++ Routine Description: Issues a GUM update to instantiate a resource type on every node. The registry update as well as the FM in-memory state update is done as a transaction within the GUM handler (NT5 clusters only). Arguments: lpszTypeName - Supplies the name of the new cluster resource type. lpszDisplayName - Supplies the display name for the new resource type. While lpszResourceTypeName should uniquely identify the resource type on all clusters, the lpszDisplayName should be a localized friendly name for the resource, suitable for displaying to administrators. lpszDllName - Supplies the name of the new resource type’s DLL. dwLooksAlive - Supplies the default LooksAlive poll interval for the new resource type in milliseconds. dwIsAlive - Supplies the default IsAlive poll interval for the new resource type in milliseconds. Return Value: ERROR_SUCCESS if successful. Win32 error otherwise. --*/ { DWORD dwStatus = ERROR_SUCCESS; PFM_RESTYPE pResType = NULL; DWORD dwTypeNameLen; DWORD dwDisplayNameLen; DWORD dwDllNameLen; DWORD dwBufferLen; LPVOID Buffer = NULL; // // Chittur Subbaraman (chitturs) - 2/8/2000 // // Rewrite this API to use a GUM handler which performs a local // transaction for NT5.1 // ClRtlLogPrint(LOG_NOISE, "[FM] FmCreateResourceType: Entry for %1!ws!...\r\n", lpszTypeName); // // If the resource type already exists, do not issue a GUM and have it fail. // pResType = OmReferenceObjectById( ObjectTypeResType, lpszTypeName ); if ( pResType ) { dwStatus = ERROR_ALREADY_EXISTS; OmDereferenceObject( pResType ); goto FnExit; } dwTypeNameLen = ( lstrlenW( lpszTypeName ) + 1 ) * sizeof( WCHAR ); dwDisplayNameLen = ( lstrlenW( lpszDisplayName ) + 1 ) * sizeof( WCHAR ); dwDllNameLen = ( lstrlenW( lpszDllName ) + 1 ) * sizeof( WCHAR ); dwBufferLen = dwTypeNameLen + dwDisplayNameLen + dwDllNameLen + 2 * sizeof( DWORD ); Buffer = LocalAlloc( LMEM_FIXED, dwBufferLen ); if ( Buffer == NULL ) { dwStatus = GetLastError(); ClRtlLogPrint(LOG_CRITICAL, "[FM] FmCreateResourceType: FmUpdateCreateResourceType for %1!ws! returned %2!u! on mem alloc...\n", lpszTypeName, dwStatus); goto FnExit; } CopyMemory( Buffer, lpszTypeName, dwTypeNameLen ); CopyMemory( ( PCHAR ) Buffer + dwTypeNameLen, lpszDisplayName, dwDisplayNameLen ); CopyMemory( ( PCHAR ) Buffer + dwTypeNameLen + dwDisplayNameLen, lpszDllName, dwDllNameLen ); CopyMemory( ( PCHAR ) Buffer + dwTypeNameLen + dwDisplayNameLen + dwDllNameLen, &dwLooksAlive, sizeof( DWORD ) ); CopyMemory( ( PCHAR ) Buffer + dwTypeNameLen + dwDisplayNameLen + dwDllNameLen + sizeof( DWORD ), &dwIsAlive, sizeof( DWORD ) ); dwStatus = GumSendUpdate( GumUpdateFailoverManager, FmUpdateCreateResourceType, dwBufferLen, Buffer ); if ( dwStatus != ERROR_SUCCESS ) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmCreateResourceType: FmUpdateCreateResourceType for %1!ws! returned %2!u!...\r\n", lpszTypeName, dwStatus); goto FnExit; } dwStatus = FmpSetPossibleNodeForResType( lpszTypeName , FALSE ); if ( dwStatus != ERROR_SUCCESS ) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmCreateResourceType: FmpSetPossibleNodeForResType for %2!ws! returned <%1!u!>...\r\n", lpszTypeName, dwStatus); goto FnExit; } pResType = OmReferenceObjectById( ObjectTypeResType, lpszTypeName ); if ( pResType ) { ClusterWideEvent( CLUSTER_EVENT_RESTYPE_ADDED, pResType ); OmDereferenceObject( pResType ); } else { if ( !FmpFMGroupsInited ) { dwStatus = ERROR_CLUSTER_NODE_NOT_READY; } else if ( FmpShutdown ) { dwStatus = ERROR_CLUSTER_NODE_SHUTTING_DOWN; } else { dwStatus = ERROR_CLUSTER_RESOURCE_TYPE_NOT_FOUND; } } FnExit: LocalFree( Buffer ); ClRtlLogPrint(LOG_NOISE, "[FM] FmCreateResourceType: Exit for %1!ws!, Status=%2!u!...\r\n", lpszTypeName, dwStatus); return( dwStatus ); } // FmCreateResourceType DWORD FmDeleteResourceType( IN LPCWSTR TypeName ) /*++ Routine Description: Issues a GUM update to delete a resource type on every node. Arguments: TypeName - Supplies the name of the cluster resource type to delete Return Value: ERROR_SUCCESS if successful. Win32 error otherwise. --*/ { PFM_RESTYPE pResType; BOOL fResourceExists = FALSE; // // Chittur Subbaraman (chitturs) - 5/9/2001 // // Make sure the resource type exists so that you can avoid a GUM if // that is not necessary. This also takes care of the case in which one node was // shutting down and so the GUM returns success and another node fails in the GUM // and gets evicted since the resource type does not exist. // pResType = OmReferenceObjectById( ObjectTypeResType, TypeName ); if ( pResType == NULL ) { ClRtlLogPrint(LOG_UNUSUAL, "[FM] FmDeleteResourceType: Resource type %1!ws! does not exist...\n", TypeName); return( ERROR_CLUSTER_RESOURCE_TYPE_NOT_FOUND ); } // // Check if any resources of this type exist, we do this to avoid a GUM since at shutdown // or startup we return ERROR_SUCCESS from the common GUM handler and that triggers // the deletion of the type from the cluster database. Ideally, we should have made the // API transactional and do this check inside GUM but for lack of time, this is the best // we can do now. // OmEnumObjects( ObjectTypeResource, FmpFindResourceType, pResType, &fResourceExists ); if ( fResourceExists ) { OmDereferenceObject ( pResType ); return ( ERROR_DIR_NOT_EMPTY ); } OmDereferenceObject ( pResType ); return(GumSendUpdate( GumUpdateFailoverManager, FmUpdateDeleteResourceType, (lstrlenW(TypeName)+1)*sizeof(WCHAR), (PVOID)TypeName )); } // FmDeleteResourceType /**** @func DWORD | FmEnumResTypeNode | Enumerate the possible nodes for a resource type @parm IN PFM_RESTYPE | pResType | Pointer to the resource type @parm IN DWORD | dwIndex | The index for this enumeration. @parm OUT PNM_NODE | pPossibleNode | The possible node. The returned node pointer will be referenced by this routine and should be dereferenced when the caller is done with it. @comm This routine helps enumerating all the nodes that a particular resource type can be supported on. @rdesc Returns a result code. ERROR_SUCCESS on success. @xref ****/ DWORD FmEnumResourceTypeNode( IN PFM_RESTYPE pResType, IN DWORD dwIndex, OUT PNM_NODE *pPossibleNode ) { PLIST_ENTRY pListEntry; DWORD i = 0; PRESTYPE_POSSIBLE_ENTRY pResTypePosEntry; DWORD Status = ERROR_NO_MORE_ITEMS; FmpMustBeOnline(); // // Chittur Subbaraman (chitturs) - 09/06/98 // // The creation and deletion of resource types are done // via atomic GUM operations. Hence these two operations // (i.e. API's) are guaranteed to be mutually exclusive. // In contrast, the resource type enumeration operation // is not mutually exclusive with either the create // or the delete operation. Thus, when a resource type is // being created/deleted, there is nothing that prevents a // client from trying to enumerate the same resource type // in a concurrent fashion, thus producing a potential race // condition. Thus, it is advisable to consider some form // of locking to avoid this situation ! // // update the list to include all nodes that now support // the resource type if (dwIndex == 0) FmpSetPossibleNodeForResType(OmObjectId(pResType), TRUE); ACQUIRE_SHARED_LOCK(gResTypeLock); pListEntry = pResType->PossibleNodeList.Flink; while (pListEntry != &pResType->PossibleNodeList) { pResTypePosEntry = CONTAINING_RECORD(pListEntry, RESTYPE_POSSIBLE_ENTRY, PossibleLinkage); if (i==dwIndex) { // // Got the right index // OmReferenceObject(pResTypePosEntry->PossibleNode); *pPossibleNode = pResTypePosEntry->PossibleNode; Status = ERROR_SUCCESS; break; } pListEntry = pListEntry->Flink; ++i; } RELEASE_LOCK(gResTypeLock); return(Status); } // FmEnumResTypeNode DWORD FmChangeResourceGroup( IN PFM_RESOURCE pResource, IN PFM_GROUP pNewGroup ) /*++ Routine Description: Moves a resource from one group to another. Arguments: Resource - Supplies the resource to move. Group - Supplies the new group that the resource should be in. Return Value: ERROR_SUCCESS if successful Win32 error code otherwise. --*/ { DWORD dwStatus; PFM_GROUP pOldGroup; FmpMustBeOnline( ); ClRtlLogPrint(LOG_NOISE, "[FM] FmChangeResourceGroup : Resource <%1!ws!> NewGroup %2!lx!\n", OmObjectId( pResource ), OmObjectId( pNewGroup)); // // Synchronize both the old and the new groups. // Lock the lowest by lowest Group Id first - to prevent deadlocks! // Note - the order of release is unimportant. // // strictly, the comparison below cannot be equal! // if ( lstrcmpiW( OmObjectId( pResource->Group ), OmObjectId( pNewGroup ) ) <= 0 ) { FmpAcquireLocalGroupLock( pResource->Group ); FmpAcquireLocalGroupLock( pNewGroup ); } else { FmpAcquireLocalGroupLock( pNewGroup ); FmpAcquireLocalGroupLock( pResource->Group ); } //remember the old group for freeing locks pOldGroup = pResource->Group; //if the resource has been marked for delete, then fail this call if (!IS_VALID_FM_RESOURCE(pResource)) { dwStatus = ERROR_RESOURCE_NOT_AVAILABLE; goto FnUnlock; } // // Check if we're moving to same group. // if (pResource->Group == pNewGroup) { dwStatus = ERROR_ALREADY_EXISTS; goto FnUnlock; } if ( pResource->Group->OwnerNode != NmLocalNode ) { // Note: FmcChangeResourceNode must release the both resource lock. dwStatus = FmcChangeResourceGroup( pResource, pNewGroup); goto FnExit; } else { dwStatus = FmpChangeResourceGroup(pResource, pNewGroup ); } FnUnlock: FmpReleaseLocalGroupLock(pNewGroup); FmpReleaseLocalGroupLock(pOldGroup); FnExit: ClRtlLogPrint(LOG_NOISE, "[FM] FmChangeResourceGroup : returned <%1!u!>\r\n", dwStatus); return(dwStatus); } // FmChangeResourceGroup DWORD FmChangeClusterName( IN LPCWSTR pszNewName, IN LPCWSTR pszOldName ) /*++ Routine Description: Changes the name of the cluster Arguments: pszNewName - Supplies the new cluster name. lpszOldName - Supplies the current name Return Value: ERROR_SUCCESS if successful. ERROR_RESOURCE_PROPERTIES STORED if the name has been changed but wont be effective until the core network name resource is brought online again. Win32 error code otherwise --*/ { DWORD dwStatus=ERROR_INVALID_PARAMETER; BOOL bNameUpdated = FALSE; PFM_RESOURCE pCoreNetNameResource = NULL; PVOID pPropList = NULL; DWORD dwPropListSize = 0; LPWSTR pszBuffer; DWORD dwLength; DWORD dwClusterHighestVersion; ClRtlLogPrint(LOG_NOISE, "[FM] FmChangeClusterName : Entry NewName=%1!ws! OldName=%2!ws!\r\n", pszNewName, pszOldName); //get all the info to be able to change the name dwStatus = FmpGetClusterNameChangeParams(pszNewName, &pCoreNetNameResource, &pPropList, &dwPropListSize); if (dwStatus != ERROR_SUCCESS) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmChangeClusterName : Failed to prepare params to set new name, Status=%1!u!\r\n", dwStatus); goto FnExit; } //first validate the name dwStatus = FmpValidateCoreNetNameChange( pCoreNetNameResource, pPropList, dwPropListSize); if (dwStatus != ERROR_SUCCESS) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmChangeClusterName : Failed validate the name, Status=%1!u!\r\n", dwStatus); goto FnExit; } dwStatus = GumSendUpdateEx(GumUpdateFailoverManager, FmUpdateChangeClusterName, 1, (lstrlenW(pszNewName)+1)*sizeof(WCHAR), pszNewName); if (dwStatus != ERROR_SUCCESS) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmChangeClusterName : GumUpdate to set new name failed with staus=%1!u!\r\n", dwStatus); goto FnExit; } bNameUpdated = TRUE; //Now notify the resource dll of the change via the set private properties call //the resource dll will try to commit the change, if it fails it will undo the change //and return failure //SS : what if it not online dwStatus = FmpCoreNetNameChange(pCoreNetNameResource, pPropList, dwPropListSize); //the core network name property/cluster name has been set //but the name change isnt effective till the resource is brought //offline and then online again, the netname dll is expected to return the correct error //ie we dont do any mapping if ((dwStatus != ERROR_SUCCESS) && (dwStatus != ERROR_RESOURCE_PROPERTIES_STORED)) { //if the resource properties have been stored or have been handled ClRtlLogPrint(LOG_CRITICAL, "[FM] FmChangeClusterName : FmpCoreNetNameChange failed status=%1!u!\r\n", dwStatus); goto FnExit; } //the name change was successful generate the event notifications //ignore the errors caused due to notifications, it is hard enough to //recover from changes spanning the service and netname that at this point //we wont bother with event notification problems //dont issue the event notification in a mixed mode cluster..a bug in win2K //cluster wide notification might result in an av // // If we are dealing with the mixed mode cluster or if the group does not have the antiaffinity // property set, then don't do anything. // NmGetClusterOperationalVersion( &dwClusterHighestVersion, NULL, NULL ); if ( ( CLUSTER_GET_MAJOR_VERSION( dwClusterHighestVersion ) < NT51_MAJOR_VERSION )) { //there is a lower version(lower than whistler) node in the cluster //we could issue a local event here but that doesnt help if the //cluster has more than two nodes..who would issue the event on other //whistler nodes? goto FnExit; } else { ClusterWideEventEx(CLUSTER_EVENT_PROPERTY_CHANGE, EP_CONTEXT_VALID|EP_FREE_CONTEXT, (PVOID)pszNewName, ((lstrlenW(pszNewName) + 1) * sizeof(WCHAR))); } FnExit: if (((dwStatus != ERROR_SUCCESS) && (dwStatus != ERROR_RESOURCE_PROPERTIES_STORED)) && (bNameUpdated)) { DWORD dwError; //we need to revert the change, netname doesnt like it //and doesnt return success or properties stored. //netname should not set the registry to revert to the old name //it should leave that to FM //If the locker fails to revert it back(hopefully that doesnt happen //often, then the admin needs to fix the problem since now we will //have a new cluster name that perhaps wont ever come online ClRtlLogPrint(LOG_UNUSUAL, "[FM] FmChangeClusterName : Making the GumUpdate to revert to old name.\r\n"); dwError = GumSendUpdateEx(GumUpdateFailoverManager, FmUpdateChangeClusterName, 1, (lstrlenW(pszOldName)+1)*sizeof(WCHAR), pszOldName); if (dwError != ERROR_SUCCESS) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmChangeClusterName : GumUpdate to revert to old name failed, status=%1!u!\r\n", dwError); //log an ominous message in the event log to say the network name //change didnt happen successfully but the name has been changed and this might require administrative //action to fix the problems CL_LOGCLUSWARNING(SERVICE_NETNAME_CHANGE_WARNING); } } if (pCoreNetNameResource) OmDereferenceObject(pCoreNetNameResource); if (pPropList) LocalFree(pPropList); ClRtlLogPrint(LOG_NOISE, "[FM] FmChangeClusterName : Exit dwStatus=%1!u!\r\n", dwStatus); return(dwStatus); } // FmChangeClusterName DWORD FmpSetResourceName( IN PFM_RESOURCE pResource, IN LPCWSTR lpszFriendlyName ) /*++ Routine Description: Updates the resource name consistently in the fm databases across the cluster. Arguments: pResource - The resource whose name is changed. lpszFriendlyName - The new name of the resource. Return Value: ERROR_SUCCESS if successful. A Win32 error code on failure. --*/ { LPCWSTR ResourceId; DWORD Status; ResourceId = OmObjectId(pResource); return(GumSendUpdateEx( GumUpdateFailoverManager, FmUpdateChangeResourceName, 2, (lstrlenW(ResourceId)+1)*sizeof(WCHAR), ResourceId, (lstrlenW(lpszFriendlyName)+1)*sizeof(WCHAR), lpszFriendlyName )); } // FmpSetResourceName DWORD FmpRegUpdateClusterName( IN LPCWSTR szNewClusterName ) /*++ Routine Description: This routine updates the cluster name in the cluster database. Arguments: szNewClusterName - A pointer to the new cluster name string. Return Value: ERROR_SUCCESS if successful. A Win32 error on failure. --*/ { return(DmSetValue( DmClusterParametersKey, CLUSREG_NAME_CLUS_NAME, REG_SZ, (CONST BYTE *)szNewClusterName, (lstrlenW(szNewClusterName)+1)*sizeof(WCHAR) )); } // FmpRegUpdateClusterName DWORD FmEvictNode( IN PNM_NODE Node ) /*++ Routine Description: Removes any references to the specified node that the FM might have put on. Arguments: Node - Supplies the node that is being evicted. Return Value: ERROR_SUCCESS if successful Win32 error code otherwise --*/ { //add a reference to the node object, the worker thread will remove this OmReferenceObject(Node); FmpPostWorkItem(FM_EVENT_NODE_EVICTED, Node, 0); return(ERROR_SUCCESS); } // FmEvictNode BOOL FmCheckNetworkDependency( IN LPCWSTR DependentNetwork ) /*++ Routine Description: Check if any IP Address resource has a dependency on a given network. Arguments: DependentNetwork - the GUID for the network to check. Return Value: TRUE - if an IP Address resource depends on the given network. FALSE otherwise. --*/ { return( FmpCheckNetworkDependency( DependentNetwork ) ); } // FmCheckNetworkDependency DWORD WINAPI FmBackupClusterDatabase( IN LPCWSTR lpszPathName ) /*++ Routine Description: Attempts a backup of the quorum log files. Arguments: lpszPathName - The directory path name where the files have to be backed up. This path must be visible to the node on which the quorum resource is online. Returns: ERROR_SUCCESS if successful. A Win32 error code on failure. --*/ { DWORD status; PFM_RESOURCE pQuoResource = NULL; FmpMustBeOnline( ); // // Chittur Subbaraman (chitturs) - 10/12/98 // // Find the quorum resource // status = FmFindQuorumResource( &pQuoResource ); if ( status != ERROR_SUCCESS ) { ClRtlLogPrint(LOG_UNUSUAL, "[FM] FmBackupQuorumLog: Could not find quorum resource...\r\n"); goto FnExit; } // // Acquire the local resource lock // FmpAcquireLocalResourceLock( pQuoResource ); // // Handle the request here if this node is the owner of the // quorum resource, else redirect it to the appropriate node. // if ( pQuoResource->Group->OwnerNode != NmLocalNode ) { // // This function will release the resource lock // status = FmcBackupClusterDatabase( pQuoResource, lpszPathName ); } else { status = FmpBackupClusterDatabase( pQuoResource, lpszPathName ); FmpReleaseLocalResourceLock( pQuoResource ); } OmDereferenceObject ( pQuoResource ); FnExit: return( status ); } // FmBackupClusterDatabase DWORD FmpBackupClusterDatabase( IN PFM_RESOURCE pQuoResource, IN LPCWSTR lpszPathName ) /*++ Routine Description: This routine first waits until the quorum resource becomes online. Then, it attempts to backup the quorum log file and the checkpoint file to the specified directory path. This function is called with the local resource lock held. Arguments: pQuoResource - Pointer to the quorum resource. lpszPathName - The directory path name where the files have to be backed up. This path must be visible to the node on which the quorum resource is online. Comments: The order in which the locks are acquired is very crucial here. Carelessness in following this strict order of acquisition can lead to potential deadlocks. The order that is followed is (1) Local resource lock - pQuoResource->Group->Lock acquired outside this function. (2) Global quorum resource lock - gQuoLock acquired here (3) Global Dm root lock - gLockDmpRoot acquired in DmBackupClusterDatabase( ). --*/ { DWORD retry = 200; DWORD Status = ERROR_SUCCESS; CL_ASSERT( pQuoResource->Group->OwnerNode == NmLocalNode ); // // Chittur Subbaraman (chitturs) - 10/12/1998 // // If quorum logging is not turned on, then log an error // and exit immediately. // if ( CsNoQuorumLogging ) { Status = ERROR_QUORUMLOG_OPEN_FAILED; CL_LOGFAILURE( ERROR_QUORUMLOG_OPEN_FAILED ); ClRtlLogPrint(LOG_NOISE, "[FM] FmpBackupClusterDatabase: Quorum logging is not turned on, can't backup...\r\n"); goto FnExit; } CheckQuorumState: ACQUIRE_EXCLUSIVE_LOCK( gQuoLock ); // // Check the state of the quorum resource. If it has failed or is // offline, release the lock and exit immediately ! // if ( pQuoResource->State == ClusterResourceFailed ) { Status = ERROR_QUORUM_RESOURCE_ONLINE_FAILED; CL_LOGFAILURE( ERROR_QUORUM_RESOURCE_ONLINE_FAILED ); ClRtlLogPrint(LOG_NOISE, "[FM] FmpBackupClusterDatabase: Quorum resource is in failed state, exiting...\r\n"); RELEASE_LOCK( gQuoLock ); goto FnExit; } // // Check if the quorum resource is online. If the quorum resource // is marked as waiting and offlinepending, it is actually online. // If the quorum resource still needs to come online, release the // lock and wait. // if ( ( ( pQuoResource->State != ClusterResourceOnline ) && ( ( pQuoResource->State != ClusterResourceOfflinePending ) || ( !( pQuoResource->Flags & RESOURCE_WAITING ) ) ) ) ) { // // We release the lock here since the quorum resource // state transition from pending needs to acquire the lock. // In general it is a bad idea to do a wait holding locks. // RELEASE_LOCK( gQuoLock ); ClRtlLogPrint(LOG_NOISE, "[FM] FmpBackupClusterDatabase: Release ghQuoLock and wait on ghQuoOnlineEvent...\r\n"); Status = WaitForSingleObject( ghQuoOnlineEvent, 500 ); if ( Status == WAIT_OBJECT_0 ) { // // If we are going to retry, wait a little bit and retry. // Sleep( 500 ); } if ( retry-- ) { goto CheckQuorumState; } CL_LOGFAILURE( ERROR_QUORUM_RESOURCE_ONLINE_FAILED ) ; ClRtlLogPrint(LOG_NOISE, "[FM] FmpBackupClusterDatabase: All retries to check for quorum resource online failed, exiting...\r\n"); return( ERROR_QUORUM_RESOURCE_ONLINE_FAILED ); } Status = DmBackupClusterDatabase( lpszPathName ); RELEASE_LOCK( gQuoLock ); FnExit: return ( Status ); } // FmpBackupClusterDatabase /**** @func WORD| FmCheckQuorumState| If the quorum resource is online on this node right now, it calls the callback and the boolean value passed in is set to FALSE. If not, the boolean is set to TRUE. @parm LPWSTR | szQuorumLogPath | A pointer to a wide string of size MAX_PATH. @parm DWORD | dwSize | The size of szQuorumLogPath in bytes. @rdesc Returns ERROR_SUCCESS for success, else returns the error code. @comm If the quorum resource is not cabaple of logging this should not be set. @xref ****/ void FmCheckQuorumState( FM_ONLINE_ONTHISNODE_CB OnlineOnThisNodeCb, PBOOL pbOfflineOnThisNode) { BOOL bLocked = FALSE; DWORD dwRetryCount = 1200; // Wait 10 min max // // SS: The mutual exclusion between this event handler and // the synchronous resource online/offline callback is // achieved by using the quorum change lock(gQuoChangeLock) // // // Chittur Subbaraman (chitturs) - 7/5/99 // // Modify group lock acquisition to release gQuoChangeLock and // retry lock acquisition. This is necessary to take care of the // case in which the quorum online notification is stuck in // FmpHandleResourceTransition waiting for the gQuoChangeLock and // some other resource in the quorum group is stuck in FmpRmOnlineResource // holding the quorum group lock and waiting for the quorum resource // to go online. // try_acquire_lock: ACQUIRE_EXCLUSIVE_LOCK( gQuoChangeLock ); FmpTryAcquireLocalGroupLock( gpQuoResource->Group, bLocked ); if ( bLocked == FALSE ) { RELEASE_LOCK( gQuoChangeLock ); ClRtlLogPrint(LOG_NOISE, "[FM] FmCheckQuorumState - Release gQuoChangeLock, sleep and retry group lock acquisition...\r\n"); if ( dwRetryCount == 0 ) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmCheckQuorumState - Unable to get quorum group lock for 10 min, halting...\r\n"); CsInconsistencyHalt( ERROR_LOCK_FAILED ); } dwRetryCount --; Sleep( 500 ); goto try_acquire_lock; } CL_ASSERT( bLocked == TRUE ); *pbOfflineOnThisNode = FALSE; if (gpQuoResource->Group->OwnerNode == NmLocalNode) { ClRtlLogPrint(LOG_NOISE, "[FM] FmCheckQuorumState - I am owner, check the state of the resource .\r\n"); //if the quorum resource is not online right now //it might be in the middle of a move and this node //might be the target of the move //set a flag to indicate that a checkpoint is necessary //when it does come online if(gpQuoResource->State != ClusterResourceOnline) { ClRtlLogPrint(LOG_NOISE, "[FM] FmCheckQuorumState - Quorum is owned but not online on this node.\r\n"); *pbOfflineOnThisNode = TRUE; } else { (*OnlineOnThisNodeCb)(); } } else { ClRtlLogPrint(LOG_NOISE, "[FM] FmCheckQuorumState - Quorum is owned by another node.\r\n"); *pbOfflineOnThisNode = TRUE; } FmpReleaseLocalGroupLock(gpQuoResource->Group); RELEASE_LOCK(gQuoChangeLock); } /**** @func WORD| FmDoesQuorumAllowJoin| If the quorum resource doesnt support multiple nodes, return error. Added to officially support local quorum resources. @rdesc Returns ERROR_SUCCESS for success, else returns the error code. @comm If the quorum resource is not cabaple of logging this should not be set. @xref ****/ DWORD FmDoesQuorumAllowJoin( IN PCWSTR pszJoinerNodeId ) { DWORD dwStatus = ERROR_SUCCESS; ACQUIRE_SHARED_LOCK(gQuoChangeLock); ClRtlLogPrint(LOG_NOISE, "[FM] FmDoesQuorumAllowJoin - Entry\r\n"); //get the characteristics for the new quorum resource dwStatus = FmpGetResourceCharacteristics(gpQuoResource, &(gpQuoResource->Characteristic)); if (dwStatus != ERROR_SUCCESS) { ClRtlLogPrint(LOG_UNUSUAL, "[FM] FmDoesQuorumAllowJoin - couldnt get quorum characteristics %1!u!\r\n", dwStatus); goto FnExit; } if ((gpQuoResource->Characteristic & CLUS_CHAR_LOCAL_QUORUM) && !(gpQuoResource->Characteristic & CLUS_CHAR_LOCAL_QUORUM_DEBUG)) { //Note :: we need an error code? dwStatus = ERROR_OPERATION_ABORTED; goto FnExit; } // JAF: RAID 513705: if a site is being started with ForceQuorum, and a 2nd site // comes online automatically after, e.g., a power failure, they will attempt to join // and will have a different idea of what constitutes a majority. // Do not allow them to join if ForceQuorum is true. if ( CsForceQuorum && ( CsForceQuorumNodes != NULL )) { if ( !FmpIsNodeInForceQuorumNodes( pszJoinerNodeId )) { ClRtlLogPrint(LOG_UNUSUAL, "[FM] Disallowing join of %1!ws! since it is not in the list of forceQuorum nodes.\n", pszJoinerNodeId ); dwStatus = ERROR_OPERATION_ABORTED; goto FnExit; } else { ClRtlLogPrint(LOG_NOISE, "[FM] Node %1!ws! is in the list of forceQuorum nodes; allowing join to proceed.\n", pszJoinerNodeId ); } } FnExit: RELEASE_LOCK(gQuoChangeLock); ClRtlLogPrint(LOG_NOISE, "[FM] FmDoesQuorumAllowJoin - Exit, Status=%1!u!\r\n", dwStatus); return(dwStatus); } /**** @func WORD| FmDoesQuorumAllowLogging| If the quorum resource doesnt support multiple nodes, return error. Added to officially support local quorum resources. @rdesc Returns ERROR_SUCCESS for success, else returns the error code. @comm If the quorum resource is not cabaple of logging this should not be set. @xref ****/ DWORD FmDoesQuorumAllowLogging( IN DWORD dwQuorumResourceCharacteristics OPTIONAL ) { DWORD dwStatus = ERROR_SUCCESS; ACQUIRE_SHARED_LOCK(gQuoChangeLock); ClRtlLogPrint(LOG_NOISE, "[FM] FmDoesQuorumAllowLogging - Entry\r\n"); if ( dwQuorumResourceCharacteristics == CLUS_CHAR_UNKNOWN ) { //get the characteristics for the new quorum resource dwStatus = FmpGetResourceCharacteristics(gpQuoResource, &(gpQuoResource->Characteristic)); if (dwStatus != ERROR_SUCCESS) { ClRtlLogPrint(LOG_UNUSUAL, "[FM] FmDoesQuorumAllowLogging - couldnt get quorum characteristics %1!u!\r\n", dwStatus); goto FnExit; } } else { gpQuoResource->Characteristic = dwQuorumResourceCharacteristics; ClRtlLogPrint(LOG_NOISE, "[FM] FmDoesQuorumAllowLogging - Using passed in characteristics 0x%1!08lx!\n", dwQuorumResourceCharacteristics); } if (gpQuoResource->Characteristic & CLUS_CHAR_LOCAL_QUORUM) { WCHAR szQuorumFileName[MAX_PATH]; //Note :: we need an error code? //if the path is an smb path name, we should allow logging //else we should disable it dwStatus = DmGetQuorumLogPath(szQuorumFileName, sizeof(szQuorumFileName)); if ((szQuorumFileName[0] == L'\\') && (szQuorumFileName[1] == L'\\')) { //assume this is an smb path //allow logging dwStatus = ERROR_SUCCESS; } else { dwStatus = ERROR_OPERATION_ABORTED; } } FnExit: RELEASE_LOCK(gQuoChangeLock); ClRtlLogPrint(LOG_NOISE, "[FM] FmDoesQuorumAllowLogging - Exit, status=%1!u!\r\n", dwStatus); return(dwStatus); } DWORD FmpCanonicalizePath( IN OUT LPWSTR lpszPath, OUT PBOOL pfIsPathUNC ) /*++ Routine Description: This routine converts a path of the form \\xyz\share to \\?\UNC\xyz\share. Arguments: lpszPathName - The path that must be converted. pfIsPathUNC - Is the path of UNC type. Return value: None. --*/ { DWORD dwStatus = ERROR_SUCCESS; WCHAR szUNC[] = L"\\\\?\\UNC\\"; WCHAR szSlash[] = L"\\\\"; WCHAR szTempPath[MAX_PATH]; LPCWSTR lpszUNCStart, lpszSlashStart; // // Optimistically assume it is a UNC path. If it is not a UNC path, we set it back to // FALSE. In a failure case, we don't care about the value of the boolean variable. // *pfIsPathUNC = TRUE; // // First check if the path starts with a "\\". If not, you are done. // lpszSlashStart = wcsstr( lpszPath, szSlash ); if ( lpszSlashStart == NULL ) { *pfIsPathUNC = FALSE; goto FnExit; } // // Next, make sure there are not more than 2 slashes any where in the path except at // the start. // lpszSlashStart = wcsstr( lpszPath+1, szSlash ); if ( lpszSlashStart != NULL ) { ClRtlLogPrint(LOG_ERROR, "[FM] FmpCanonicalizePath: Supplied path %1!ws! is invalid...\n", lpszPath); dwStatus = ERROR_INVALID_PARAMETER; goto FnExit; } // // Next, check if the path already has UNC at the beginning. If so, you are done. // lpszUNCStart = wcsstr( lpszPath, szUNC ); if ( lpszUNCStart != NULL ) { if ( lpszUNCStart != lpszPath ) { ClRtlLogPrint(LOG_ERROR, "[FM] FmpCanonicalizePath: Supplied path %1!ws! is invalid...\n", lpszPath); dwStatus = ERROR_INVALID_PARAMETER; } goto FnExit; } lstrcpy( szTempPath, szUNC ); lstrcat( szTempPath, lpszPath+2 ); lstrcpy( lpszPath, szTempPath ); FnExit: ClRtlLogPrint(LOG_NOISE, "[FM] FmpCanonicalizePath: Exit Status %1!u!, Return path = %2!ws!\n", dwStatus, lpszPath); return ( dwStatus ); } // FmpCanonicalizePath DWORD FmpGetClusterNameChangeParams( IN LPCWSTR lpszNewName, OUT PFM_RESOURCE *ppCoreNetNameResource, OUT PVOID *ppPropList, OUT LPDWORD pdwPropListSize ) { PFM_RESOURCE pResource = NULL; DWORD dwSize = 0; DWORD dwStatus; LPWSTR lpszClusterNameResource = NULL; CLUSPROP_BUFFER_HELPER ListEntry; PVOID pPropList = NULL; DWORD cbListSize = 0; DWORD dwBufferSize; //initialize the returns *ppCoreNetNameResource = NULL; *pdwPropListSize = 0; *ppPropList = NULL; ClRtlLogPrint(LOG_NOISE, "[FM] FmpGetClusterNameChangeParams: Entry !\n"); dwStatus = DmQuerySz( DmClusterParametersKey, CLUSREG_NAME_CLUS_CLUSTER_NAME_RES, &lpszClusterNameResource, &dwSize, &dwSize ); if ( dwStatus != ERROR_SUCCESS ) { ClRtlLogPrint(LOG_CRITICAL, "[FM] FmpGetClusterNameChangeParams: Failed to get cluster name resource from registry, error %1!u!...\n", dwStatus); goto FnExit; } // // Reference the specified resource ID. // pResource = OmReferenceObjectById( ObjectTypeResource, lpszClusterNameResource ); if ( pResource == NULL ) { dwStatus = ERROR_RESOURCE_NOT_FOUND; ClRtlLogPrint(LOG_CRITICAL, "[FM] FmGetClusterNameChangeParams: Failed to find cluster name resource, %1!u!...\n", dwStatus); goto FnExit; } dwBufferSize = sizeof( ListEntry.pList->nPropertyCount ) + sizeof( *ListEntry.pName ) + ALIGN_CLUSPROP( ( lstrlenW( CLUSREG_NAME_NET_NAME ) + 1 ) * sizeof( WCHAR ) ) + sizeof( *ListEntry.pStringValue ) + ALIGN_CLUSPROP( ( lstrlenW( lpszNewName ) + 1 ) * sizeof( WCHAR ) ) + sizeof( *ListEntry.pSyntax ); ListEntry.pb = (PBYTE) LocalAlloc( LPTR, dwBufferSize ); if ( ListEntry.pb == NULL ) { dwStatus = GetLastError(); ClRtlLogPrint(LOG_CRITICAL, "[FM] FmGetClusterNameChangeParams: Error %1!u! in allocating memory...\n", dwStatus); goto FnExit; } pPropList = ListEntry.pb; ListEntry.pList->nPropertyCount = 1; cbListSize += sizeof( ListEntry.pList->nPropertyCount ); ListEntry.pb += sizeof( ListEntry.pList->nPropertyCount ); ListEntry.pName->Syntax.dw = CLUSPROP_SYNTAX_NAME; ListEntry.pName->cbLength = ( lstrlenW( CLUSREG_NAME_NET_NAME ) + 1 ) * sizeof( WCHAR ); lstrcpyW( ListEntry.pName->sz, CLUSREG_NAME_NET_NAME ); cbListSize += sizeof( *ListEntry.pName ) + ALIGN_CLUSPROP( ListEntry.pName->cbLength ); ListEntry.pb += sizeof( *ListEntry.pName ) + ALIGN_CLUSPROP( ListEntry.pName->cbLength ); ListEntry.pStringValue->Syntax.dw = CLUSPROP_SYNTAX_LIST_VALUE_SZ; ListEntry.pStringValue->cbLength = ( lstrlenW( lpszNewName ) + 1 ) * sizeof( WCHAR ); lstrcpyW( ListEntry.pStringValue->sz, lpszNewName ); cbListSize += sizeof( *ListEntry.pStringValue ) + ALIGN_CLUSPROP( ListEntry.pName->cbLength ); ListEntry.pb += sizeof( *ListEntry.pStringValue ) + ALIGN_CLUSPROP( ListEntry.pName->cbLength ); ListEntry.pSyntax->dw = CLUSPROP_SYNTAX_ENDMARK; cbListSize += sizeof( *ListEntry.pSyntax ); ListEntry.pb += sizeof( *ListEntry.pSyntax ); *ppCoreNetNameResource = pResource; *ppPropList = pPropList; *pdwPropListSize = dwBufferSize; FnExit: if (lpszClusterNameResource) LocalFree(lpszClusterNameResource); if (dwStatus != ERROR_SUCCESS) { if (pResource) OmDereferenceObject(pResource); if (pPropList) LocalFree(pPropList); } ClRtlLogPrint(LOG_NOISE, "[FM] FmpGetClusterNameChangeParams: Exit !\n"); return(dwStatus); } /**** @func DWORD| FmpValideCoreNetNameChange| Send a control code to the core netname resource to validate the change. @param IN PFM_RESOURCE | pResource | A pointer to the core netname resource. @param IN PVOID | pPropList | Pointer to a property list. @param IN DWORD | cbListSize | Size of the property list. @rdesc Returns ERROR_SUCCESS for success, else returns the error code. @comm @xref ****/ DWORD FmpValidateCoreNetNameChange( IN PFM_RESOURCE pResource, IN PVOID pPropList, IN DWORD cbListSize ) { DWORD dwBytesReturned; DWORD dwRequired; DWORD dwStatus; ClRtlLogPrint(LOG_NOISE, "[FM] FmpValidateCoreNetNameChange : Calling Core netname to validate\r\n"); dwStatus = FmResourceControl( pResource, NULL, CLUSCTL_RESOURCE_VALIDATE_PRIVATE_PROPERTIES, (PUCHAR)pPropList, cbListSize, NULL, 0, &dwBytesReturned, &dwRequired ); return(dwStatus); } /**** @func WORD| FmCoreNetNameChange| Asks the core network name resource to perform all the network name change related operations. @param IN PFM_RESOURCE | pResource | A pointer to the core netname resource. @param IN PVOID | pPropList | Pointer to a property list. @param IN DWORD | cbListSize | Size of the property list. @rdesc Returns ERROR_SUCCESS for success, else returns the error code. @comm @xref ****/ DWORD FmpCoreNetNameChange( IN PFM_RESOURCE pResource, IN PVOID pPropList, IN DWORD cbListSize ) { DWORD dwBytesReturned; DWORD dwRequired; DWORD dwStatus; ClRtlLogPrint(LOG_NOISE, "[FM] FmpCoreNetNameChange : Invoking the core netname resource dll\r\n"); dwStatus = FmResourceControl( pResource, NULL, CLUSCTL_RESOURCE_CLUSTER_NAME_CHANGED, (PUCHAR)pPropList, cbListSize, NULL, 0, &dwBytesReturned, &dwRequired ); return(dwStatus); }