Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1574 lines
43 KiB

/*++
Copyright (c) 1996 Microsoft Corporation
Module Name:
dminit.c
Abstract:
Contains the initialization code for the Cluster Database Manager
Author:
John Vert (jvert) 24-Apr-1996
Revision History:
--*/
#include "dmp.h"
//
// Global Data
//
HKEY DmpRoot;
HKEY DmpRootCopy;
LIST_ENTRY KeyList;
CRITICAL_SECTION KeyLock;
HDMKEY DmClusterParametersKey;
HDMKEY DmResourcesKey;
HDMKEY DmResourceTypesKey;
HDMKEY DmGroupsKey;
HDMKEY DmNodesKey;
HDMKEY DmNetworksKey;
HDMKEY DmNetInterfacesKey;
HDMKEY DmQuorumKey;
HANDLE ghQuoLogOpenEvent=NULL;
#if NO_SHARED_LOCKS
CRITICAL_SECTION gLockDmpRoot;
#else
RTL_RESOURCE gLockDmpRoot;
#endif
BOOL gbIsQuoLoggingOn=FALSE;
HANDLE ghDiskManTimer=NULL;//disk management timer
PFM_RESOURCE gpQuoResource=NULL; //set when DMFormNewCluster is completed
HANDLE ghCheckpointTimer = NULL; //timer for periodic checkpointing
BOOL gbDmInited = FALSE; //set to TRUE when all phases of dm initialization are over
extern HLOG ghQuoLog;
BOOL gbDmpShutdownUpdates = FALSE;
//define public cluster key value names
const WCHAR cszPath[]= CLUSREG_NAME_QUORUM_PATH;
const WCHAR cszMaxQuorumLogSize[]=CLUSREG_NAME_QUORUM_MAX_LOG_SIZE;
const WCHAR cszParameters[] = CLUSREG_KEYNAME_PARAMETERS;
//other const strings
const WCHAR cszQuoFileName[]=L"quolog.log";
const WCHAR cszQuoTombStoneFile[]=L"quotomb.stn";
const WCHAR cszTmpQuoTombStoneFile[]=L"quotomb.tmp";
GUM_DISPATCH_ENTRY DmGumDispatchTable[] = {
{3, (PGUM_DISPATCH_ROUTINE1)DmpUpdateCreateKey},
{4, (PGUM_DISPATCH_ROUTINE1)DmpUpdateSetSecurity}
};
//
// Global data for interfacing with registry watcher thread
//
HANDLE hDmpRegistryFlusher=NULL;
HANDLE hDmpRegistryEvent=NULL;
HANDLE hDmpRegistryRestart=NULL;
DWORD
DmpRegistryFlusher(
IN LPVOID lpThreadParameter
);
//
// Local function prototypes
//
VOID
DmpInvalidateKeys(
VOID
);
VOID
DmpReopenKeys(
VOID
);
DWORD
DmpLoadHive(
IN LPCWSTR Path
);
typedef struct _DMP_KEY_DEF {
HDMKEY *pKey;
LPWSTR Name;
} DMP_KEY_DEF;
DMP_KEY_DEF DmpKeyTable[] = {
{&DmResourcesKey, CLUSREG_KEYNAME_RESOURCES},
{&DmResourceTypesKey, CLUSREG_KEYNAME_RESOURCE_TYPES},
{&DmQuorumKey, CLUSREG_KEYNAME_QUORUM},
{&DmGroupsKey, CLUSREG_KEYNAME_GROUPS},
{&DmNodesKey, CLUSREG_KEYNAME_NODES},
{&DmNetworksKey, CLUSREG_KEYNAME_NETWORKS},
{&DmNetInterfacesKey, CLUSREG_KEYNAME_NETINTERFACES}
};
DWORD
DmInitialize(
VOID
)
/*++
Routine Description:
Inits the config database manager
Arguments:
None
Return Value:
ERROR_SUCCESS if successful
Win32 error code otherwise
--*/
{
BOOL Success;
DWORD Status = ERROR_SUCCESS;
DWORD dwOut;
ClRtlLogPrint(LOG_NOISE,"[DM] Initialization\n");
InitializeListHead(&KeyList);
InitializeCriticalSection(&KeyLock);
//create a critical section for locking the database while checkpointing
INITIALIZE_LOCK(gLockDmpRoot);
//create a named event that is used for waiting for quorum resource
//to go online
ghQuoLogOpenEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!ghQuoLogOpenEvent)
{
CL_UNEXPECTED_ERROR((Status = GetLastError()));
goto FnExit;
}
Success = DmpInitNotify();
CL_ASSERT(Success);
if (!Success)
{
Status = GetLastError();
goto FnExit;
}
//find out if the databasecopy was in progresss on last death
DmpGetDwordFromClusterServer(L"ClusterDatabaseCopyInProgress", &dwOut, 0);
LoadClusterDatabase:
//
// Open key to root of cluster.
//
Status = RegOpenKeyW(HKEY_LOCAL_MACHINE,
DmpClusterParametersKeyName,
&DmpRoot);
//
// If the key was not found, go load the database.
//
if (Status == ERROR_FILE_NOT_FOUND) {
WCHAR Path[MAX_PATH];
WCHAR BkpPath[MAX_PATH];
WCHAR *p;
Status = GetModuleFileName(NULL, Path, MAX_PATH);
//
// GetModuleFileName may not NULL terminate the Path.
//
Path [ RTL_NUMBER_OF ( Path ) - 1 ] = UNICODE_NULL;
if (Status == 0) {
Status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[DM] Couldn't find cluster database, status=%1!u!\n",
Status);
goto FnExit;
}
//get the name of the cluster database
p=wcsrchr(Path, L'\\');
if (p == NULL)
{
Status = ERROR_FILE_NOT_FOUND;
CL_UNEXPECTED_ERROR(Status);
goto FnExit;
}
//see if we should load the hive from the old one or the bkp file
*p = L'\0';
wcscpy(BkpPath, Path);
#ifdef OLD_WAY
wcscat(Path, L"\\CLUSDB");
wcscat(BkpPath, L"\\CLUSTER_DATABASE_TMPBKP_NAME");
#else // OLD_WAY
wcscat(Path, L"\\"CLUSTER_DATABASE_NAME );
wcscat(BkpPath, L"\\"CLUSTER_DATABASE_TMPBKP_NAME);
#endif // OLD_WAY
if (dwOut)
{
//the backip file must exist
ClRtlLogPrint(LOG_NOISE,
"[DM] DmInitialize:: DatabaseCopy was in progress on last death, get hive from %1!ws!!\n",
BkpPath);
//set file attributes of the BkpPath
if (!QfsSetFileAttributes(BkpPath, FILE_ATTRIBUTE_NORMAL))
{
Status = GetLastError();
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmInitialize:: SetFileAttrib on BkpPath %1!ws! failed, Status=%2!u!\n",
BkpPath, Status);
goto FnExit;
}
//ClRtlCopyFileAndFlushBuffers preserves the attributes on the original file
if (!QfsClRtlCopyFileAndFlushBuffers(BkpPath, Path))
{
Status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmInitialize:: Databasecopy was in progress,Failed to copy %1!ws! to %2!ws!, Status=%3!u!\n",
BkpPath, Path, Status);
//set the file attribute on the backup, so that
//nobody mucks with it without knowing what they are
//doing
QfsSetFileAttributes(BkpPath, FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_READONLY);
goto FnExit;
}
//now we can reset the DatabaseCopyInProgress value in the registry
//set databaseCopyInProgress key to FALSE
//This will flush the key as well
Status = DmpSetDwordInClusterServer( L"ClusterDatabaseCopyInProgress", 0);
if (Status != ERROR_SUCCESS)
{
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmInitialize:: Failed to reset ClusterDatabaseCopyInProgress, Status=%1!u!\n",
Status);
goto FnExit;
}
//Now we can delete the backup path, since the key has been flushed
if (!QfsDeleteFile(BkpPath))
{
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmInitialize:: Failed to delete the backup when it wasnt needed,Status=%1!u!\n",
GetLastError());
//this is not fatal so we ignore the error
}
}
else
{
//the backup file might exist
//this is true when safe copy makes a backup but hasnt
//set the value DatabaseCopyInProgress in the registry
//if it does delete it
//set file attributes of the BkpPath
if (!QfsSetFileAttributes(BkpPath, FILE_ATTRIBUTE_NORMAL))
{
//errors are not fatal, we just ignore them
//this may fail because the path doesnt exist
}
//Now we can delete the backup path, since the key has been flushed
//this is not fatal so we ignore the error
if (QfsDeleteFile(BkpPath))
{
ClRtlLogPrint(LOG_NOISE,
"[DM] DmInitialize:: Deleted the unneeded backup of the cluster database\n");
}
}
Status = DmpLoadHive(Path);
if (Status != ERROR_SUCCESS)
{
ClRtlLogPrint(LOG_CRITICAL,
"[DM] Couldn't load cluster database\n");
CsLogEventData(LOG_CRITICAL,
DM_DATABASE_CORRUPT_OR_MISSING,
sizeof(Status),
&Status);
goto FnExit;
}
Status = RegOpenKeyW(HKEY_LOCAL_MACHINE,
DmpClusterParametersKeyName,
&DmpRoot);
//
// HACKHACK John Vert (jvert) 6/3/1997
// There is a bug in the registry with refresh
// where the Parent field in the root cell doesn't
// get flushed to disk, so it gets blasted if we
// do a refresh. Then we crash in unload. So flush
// out the registry to disk here to make sure the
// right Parent field gets written to disk.
//
if (Status == ERROR_SUCCESS) {
DWORD Dummy=0;
//
// Make something dirty in the root
//
RegSetValueEx(DmpRoot,
L"Valid",
0,
REG_DWORD,
(PBYTE)&Dummy,
sizeof(Dummy));
RegDeleteValue(DmpRoot, L"Valid");
Status = RegFlushKey(DmpRoot);
}
} else {
//if the hive is already loaded we unload and reload it again
//to make sure that it is loaded with the right flags and
//also to make sure that the backup copy is used in case
//of failures
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmInitialize: The hive was loaded- rollback, unload and reload again\n");
//BUGBUG:: currently the unload flushes the hive, ideally we
//would like to unload it without flushing it
//This way a part transaction wont be a part of the hive
//However, if somebody messes with the cluster hive using
//regedt32 and if reg_no_lazy flush is not specified, some
//changes might get flushed to the hive.
//We can try and do the rollback in any case,
//the rollback will fail if the registry wasnt loaded with the
//reg_no_lazy_flush flag.
//unload it and then proceed to reload it
//this will take care of situations where a half baked clusdb
//gets loaded because of failures
Status = DmRollbackRegistry();
if (Status != ERROR_SUCCESS)
{
//we ignore the error
Status = ERROR_SUCCESS;
}
RegCloseKey(DmpRoot);
if ( DmpRootCopy != NULL ) {
RegCloseKey(DmpRootCopy);
}
DmpRoot = DmpRootCopy = NULL;
Status = DmpUnloadHive();
if (Status != ERROR_SUCCESS)
{
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmInitialize: DmpUnloadHive failed, Status=%1!u!\n",
Status);
goto FnExit;
}
goto LoadClusterDatabase;
}
if (Status != ERROR_SUCCESS) {
CL_UNEXPECTED_ERROR(Status);
goto FnExit;
}
Status = RegOpenKeyW(HKEY_LOCAL_MACHINE,
DmpClusterParametersKeyName,
&DmpRootCopy);
if ( Status != ERROR_SUCCESS ) {
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmInitialize:: Failed to open copy of registry key,Status=%1!u!\n",
Status);
goto FnExit;
}
//
// Create the registry watcher thread
//
Status = DmpStartFlusher();
if (Status != ERROR_SUCCESS) {
goto FnExit;
}
//
// Open the cluster keys
//
Status = DmpOpenKeys(MAXIMUM_ALLOWED);
if (Status != ERROR_SUCCESS) {
CL_UNEXPECTED_ERROR( Status );
goto FnExit;
}
FnExit:
return(Status);
}//DmInitialize
DWORD
DmpRegistryFlusher(
IN LPVOID lpThreadParameter
)
/*++
Routine Description:
Registry watcher thread for explicitly flushing changes.
Arguments:
lpThreadParameter - not used
Return Value:
None.
--*/
{
DWORD Status;
HANDLE hEventFullSubtree = NULL;
HANDLE hTimer = NULL;
HANDLE hEventTopOnly = NULL;
HANDLE WaitArray[5];
LARGE_INTEGER DueTime;
BOOL Dirty = FALSE;
BOOL subtreeNeedsReg = TRUE;
BOOL topNeedsReg = TRUE;
//
// Create a notification event and a delayed timer for lazy flushing.
//
hEventFullSubtree = CreateEvent(NULL, TRUE, FALSE, NULL);
if (hEventFullSubtree == NULL) {
Status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmpRegistryFlusher couldn't create notification event %1!d!\n",
Status);
goto error_exit;
}
hTimer = CreateWaitableTimer(NULL, FALSE, NULL);
if (hTimer == NULL) {
Status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmpRegistryFlusher couldn't create notification timer %1!d!\n",
Status);
goto error_exit;
}
hEventTopOnly = CreateEvent(NULL, TRUE, FALSE, NULL);
if (hEventTopOnly == NULL) {
Status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmpRegistryFlusher couldn't create notification event %1!d!\n",
Status);
goto error_exit;
}
WaitArray[0] = hDmpRegistryEvent;
WaitArray[1] = hEventFullSubtree;
WaitArray[2] = hTimer;
WaitArray[3] = hDmpRegistryRestart;
WaitArray[4] = hEventTopOnly;
while (TRUE) {
//
// Set up a registry notification on DmpRoot. We acquire the lock here to
// make sure that rollback or install is not messing with the database
// while we are trying to get a notification.
//
Status = ERROR_SUCCESS;
ACQUIRE_EXCLUSIVE_LOCK(gLockDmpRoot);
// Because we are now waiting on 2 separate RegNotify's, we can't just
// reregister for notications on every iteration. Need to see which
// registration is "stale".
if ( subtreeNeedsReg ) {
Status = RegNotifyChangeKeyValue(DmpRoot,
TRUE,
REG_LEGAL_CHANGE_FILTER,
hEventFullSubtree,
TRUE);
subtreeNeedsReg = FALSE;
}
if ( topNeedsReg && Status == ERROR_SUCCESS ) {
Status = RegNotifyChangeKeyValue(DmpRootCopy,
FALSE, // not whole subtree
REG_LEGAL_CHANGE_FILTER,
hEventTopOnly,
TRUE);
topNeedsReg = FALSE;
}
RELEASE_LOCK(gLockDmpRoot);
if (Status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmpRegistryFlusher couldn't register for notifications %1!d!\n",
Status);
break;
}
//
// Wait for something to happen.
//
Status = WaitForMultipleObjects(sizeof(WaitArray)/sizeof(WaitArray[0]),
WaitArray,
FALSE,
(DWORD)-1);
switch (Status) {
case 0:
ClRtlLogPrint(LOG_NOISE,"[DM] DmpRegistryFlusher: got 0\r\n");
//
// We have been asked to stop, clean up and exit
//
Status = ERROR_SUCCESS;
if (Dirty) {
//
// Make sure any changes that we haven't gotten around to flushing
// get flushed now.
//
DmCommitRegistry();
}
ClRtlLogPrint(LOG_NOISE,"[DM] DmpRegistryFlusher: exiting\r\n");
goto error_exit;
break;
case 1:
// The subtree RegNotify is now stale and needs to be reregistered.
subtreeNeedsReg = TRUE;
//
// A registry change has occurred. Set our timer to
// go off in 5 seconds. At that point we will do the
// actual flush.
//
//ClRtlLogPrint(LOG_NOISE,"[DM] DmpRegistryFlusher: got 1\r\n");
DueTime.QuadPart = -5 * 10 * 1000 * 1000;
if (!SetWaitableTimer(hTimer,
&DueTime,
0,
NULL,
NULL,
FALSE)) {
//
// Some error occurred, go ahead and flush now.
//
Status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmpRegistryFlusher failed to set lazy flush timer %1!d!\n",
Status);
#if DBG
CL_ASSERT(FALSE);
#endif
DmCommitRegistry();
Dirty = FALSE;
} else {
Dirty = TRUE;
}
break;
case 2:
//
// The lazy flush timer has gone off, commit the registry now.
//
//ClRtlLogPrint(LOG_NOISE,"[DM] DmpRegistryFlusher: got 2\r\n");
DmCommitRegistry();
Dirty = FALSE;
break;
case 3:
//
// DmpRoot has been changed, restart the loop with the new handle.
//
ClRtlLogPrint(LOG_NOISE,"[DM] DmpRegistryFlusher: restarting\n");
// Because the HKEYs have been closed, both RegNotify's are
// now stale and need to be reregistered.
subtreeNeedsReg = topNeedsReg = TRUE;
break;
case 4:
//
// Since this registry change may have come from a DM update, the
// parameters under the Cluster key may have changed on another node.
// Update our in-memory variables to reflect the current registry settings.
//
CsRefreshGlobalsFromRegistry();
// The top RegNotify is now stale and needs to be reregistered.
topNeedsReg = TRUE;
break;
default:
//
// Something very odd has happened
//
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmpRegistryFlusher got error %1!d! from WaitForMultipleObjects\n",
Status);
goto error_exit;
} // switch
} // while TRUE
error_exit:
if ( hEventTopOnly != NULL )
CloseHandle(hEventTopOnly);
if ( hTimer != NULL )
CloseHandle(hTimer);
if ( hEventFullSubtree != NULL )
CloseHandle(hEventFullSubtree);
if (Status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmpRegistryFlusher exiting abnormally, status %1!d!\n",
Status);
}
return(Status);
}
DWORD
DmJoin(
IN RPC_BINDING_HANDLE RpcBinding,
OUT DWORD *StartSeq
)
/*++
Routine Description:
Performs the join and synchronization process for the
database manager.
Arguments:
RpcBinding - Supplies an RPC binding handle to the Join Master
Return Value:
ERROR_SUCCESS if successful
Win32 error otherwise.
--*/
{
DWORD Status;
DWORD GumSequence;
DWORD CurrentSequence;
//
// Register our update handler.
//
GumReceiveUpdates(TRUE,
GumUpdateRegistry,
DmpUpdateHandler,
DmWriteToQuorumLog,
sizeof(DmGumDispatchTable)/sizeof(GUM_DISPATCH_ENTRY),
DmGumDispatchTable,
NULL);
retry:
CurrentSequence = DmpGetRegistrySequence();
Status = GumBeginJoinUpdate(GumUpdateRegistry, &GumSequence);
if (Status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL,
"[DM] GumBeginJoinUpdate failed %1!d!\n",
Status);
return(Status);
}
/*
if (CurrentSequence == GumSequence) {
//
// Our registry sequence already matches. No need to slurp
// down a new copy.
//
ClRtlLogPrint(LOG_NOISE,
"[DM] DmJoin: registry database is up-to-date\n");
} else
*/
//SS: always get the database irrespective of the sequence numbers
//this is because transactions may be lost in the log file due
//to the fact that it is not write through and because of certain
//race conditions in down notifications vs gum failure conditions.
{
ClRtlLogPrint(LOG_NOISE,
"[DM] DmJoin: getting new registry database\n");
Status = DmpSyncDatabase(RpcBinding, NULL);
if (Status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmJoin: DmpSyncDatabase failed %1!d!\n",
Status);
return(Status);
}
}
//
// Issue GUM join update
//
Status = GumEndJoinUpdate(GumSequence,
GumUpdateRegistry,
DmUpdateJoin,
0,
NULL);
if (Status == ERROR_CLUSTER_DATABASE_SEQMISMATCH) {
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] GumEndJoinUpdate with sequence %1!d! failed with a sequence mismatch\n",
GumSequence);
goto retry;
} else if (Status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL,
"[DM] GumEndJoinUpdate with sequence %1!d! failed with status %2!d!\n",
GumSequence,
Status);
return(Status);
}
*StartSeq = GumSequence;
return(ERROR_SUCCESS);
} // DmJoin
/*
DWORD
DmFormNewCluster(
VOID
)
{
DWORD Status;
//
// Set the current GUM sequence to be one more than the one in the registry.
//
// SS: this will be the one to be used for the next gum transaction,
// it should be one than the current as the logger discards the first of
// every record the same transaction number to resolve changes made when the
// locker/logger node dies in the middle of a transaction
GumSetCurrentSequence(GumUpdateRegistry, (DmpGetRegistrySequence()+1));
return(ERROR_SUCCESS);
} // DmFormNewCluster
*/
DWORD
DmFormNewCluster(
VOID
)
/*++
Routine Description:
This routine sets the gum sequence number from the registry before
logs are unrolled and prepares the quorum object for quorum logging.
It also hooks events for node up/down notifications.
Arguments:
None.
Return Value:
ERROR_SUCCESS if successful.
A Win32 error code on failure.
--*/
{
DWORD dwError=ERROR_SUCCESS;
//
// Set the current GUM sequence to be one more than the one in the registry.
//
// SS: this will be the one to be used for the next gum transaction,
// it should be one than the current as the logger discards the first of
// every record the same transaction number to resolve changes made when the
// locker/logger node dies in the middle of a transaction
GumSetCurrentSequence(GumUpdateRegistry, (DmpGetRegistrySequence()+1));
//
// Register our update handler.
//
GumReceiveUpdates(FALSE,
GumUpdateRegistry,
DmpUpdateHandler,
DmWriteToQuorumLog,
sizeof(DmGumDispatchTable)/sizeof(GUM_DISPATCH_ENTRY),
DmGumDispatchTable,
NULL);
//hook the callback for node related notification with the event processor
if (dwError = DmpHookEventHandler())
{
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmUpdateFormNewCluster: DmpHookEventHandler failed 0x!08lx!\r\n",
dwError);
goto FnExit;
};
//get the quorum resource and hook the callback for notification on quorum resource
if (dwError = DmpHookQuorumNotify())
{
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmUpdateFormNewCluster: DmpHookQuorumNotify failed 0x%1!08lx!\r\n",
dwError);
goto FnExit;
};
//SS: if this procedure is successfully completed gpQuoResource is NON NULL.
FnExit:
return(dwError);
} // DmUpdateFormNewCluster
DWORD
DmUpdateFormNewCluster(
VOID
)
/*++
Routine Description:
This routine updates the cluster registry after the quorum resource has
been arbitrated as part of forming a new cluster. The database manager
is expected to read logs or do whatever it needs to update the current
state of the registry - presumably using logs that are written to the
quorum resource. This implies that the quorum resource represents some
form of stable storage.
Arguments:
None.
Return Value:
ERROR_SUCCESS if successful.
A Win32 error code on failure.
--*/
{
DWORD dwError=ERROR_SUCCESS;
BOOL bAreAllNodesUp = TRUE; //assume all nodes are up
//since we havent been logging as yet, take a checkpoint
if (ghQuoLog)
{
//get a checkpoint database
ClRtlLogPrint(LOG_NOISE,
"[DM] DmUpdateFormNewCluster - taking a checkpoint\r\n");
//
// Chittur Subbaraman (chitturs) - 6/3/99
//
// Make sure the gLockDmpRoot is held before LogCheckPoint is called
// so as to maintain the ordering between this lock and the log lock.
//
ACQUIRE_SHARED_LOCK(gLockDmpRoot);
dwError = LogCheckPoint(ghQuoLog, TRUE, NULL, 0);
RELEASE_LOCK(gLockDmpRoot);
if (dwError != ERROR_SUCCESS)
{
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmUpdateFormNewCluster - Failed to take a checkpoint in the log file\r\n");
CL_UNEXPECTED_ERROR(dwError);
}
}
//if all nodes are not up, turn quorum logging on
if ((dwError = OmEnumObjects(ObjectTypeNode, DmpNodeObjEnumCb, &bAreAllNodesUp, NULL))
!= ERROR_SUCCESS)
{
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmUpdateFormNewCluster : OmEnumObjects returned 0x%1!08lx!\r\n",
dwError);
goto FnExit;
}
if (!bAreAllNodesUp)
{
ClRtlLogPrint(LOG_NOISE,
"[DM] DmUpdateFormNewCluster - some node down\r\n");
gbIsQuoLoggingOn = TRUE;
}
//add a timer to monitor disk space, should be done after we have formed.
ghDiskManTimer = CreateWaitableTimer(NULL, FALSE, NULL);
if (!ghDiskManTimer)
{
CL_LOGFAILURE(dwError = GetLastError());
goto FnExit;
}
AddTimerActivity(ghDiskManTimer, DISKSPACE_MANAGE_INTERVAL, 1, DmpDiskManage, NULL);
gbDmInited = TRUE;
FnExit:
return (dwError);
} // DmFormNewCluster
/****
@func DWORD | DmPauseDiskManTimer| The disk manager timer activity to monitor
space on the quorum disk is set to a puased state.
@rdesc Returns ERROR_SUCCESS on success. Else returns the error code.
@comm This is called while the quorum resource is being changed.
@xref <f DmRestartDiskManTimer>
****/
DWORD DmPauseDiskManTimer()
{
DWORD dwError=ERROR_SUCCESS;
if (ghDiskManTimer)
dwError = PauseTimerActivity(ghDiskManTimer);
return(dwError);
}
/****
@func DWORD | DmRestartDiskManTimer| This disk manager activity to monitor
space on the quorum disk is set back to activated state.
@rdesc Returns ERROR_SUCCESS on success. Else returns the error code.
@comm This is called after the quorum resource has been changed.
@xref <f DmPauseDiskManTimer>
****/
DWORD DmRestartDiskManTimer()
{
DWORD dwError=ERROR_SUCCESS;
if (ghDiskManTimer)
dwError = UnpauseTimerActivity(ghDiskManTimer);
return(dwError);
}
/****
@func DWORD | DmRollChanges| This waits for the quorum resource to come online at
initialization when a cluster is being formed. The changes in the quorum
log file are applied to the local cluster database.
@rdesc Returns ERROR_SUCCESS on success. Else returns the error code.
@comm This allows for partitions in time.
@xref
****/
DWORD DmRollChanges()
{
DWORD dwError=ERROR_SUCCESS;
//before applying the changes validate that this quorum resource is the real one
if ((dwError = DmpChkQuoTombStone()) != ERROR_SUCCESS)
{
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmRollChanges: DmpChkQuoTombStone() failed 0x%1!08lx!\r\n",
dwError);
goto FnExit;
}
if ((dwError = DmpApplyChanges()) != ERROR_SUCCESS)
{
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmRollChanges: DmpApplyChanges() failed 0x%1!08lx!\r\n",
dwError);
goto FnExit;
}
//ss: this is here since lm doesnt know about the ownership of quorum
//disks today
//call DmpCheckSpace
if ((dwError = DmpCheckDiskSpace()) != ERROR_SUCCESS)
{
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmRollChanges: DmpCheckDiskSpace() failed 0x%1!08lx!\r\n",
dwError);
goto FnExit;
}
FnExit:
return(dwError);
}
DWORD DmShutdown()
{
DWORD dwError;
ClRtlLogPrint(LOG_NOISE,
"[Dm] DmShutdown\r\n");
//this will close the timer handle
if (ghDiskManTimer) RemoveTimerActivity(ghDiskManTimer);
if (gpQuoResource)
{
// DmFormNewCluster() completed
//
// Deregister from any further GUM updates
//
//GumIgnoreUpdates(GumUpdateRegistry, DmpUpdateHandler);
}
//unhook the callback for notification on quorum resource
if (dwError = DmpUnhookQuorumNotify())
{
//just log the error as we are shutting down
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmShutdown: DmpUnhookQuorumNotify failed 0x%1!08lx!\r\n",
dwError);
}
//if the quorum log is open close it
if (ghQuoLog)
{
LogClose(ghQuoLog);
ghQuoLog = NULL;
//dont try and log after this
gbIsQuoLoggingOn = FALSE;
}
//close the event created for notification of the quorum resource to
//go online
if (ghQuoLogOpenEvent)
{
//wait any thread blocked on this
SetEvent(ghQuoLogOpenEvent);
CloseHandle(ghQuoLogOpenEvent);
ghQuoLogOpenEvent = NULL;
}
//
// Shut down the registry flusher thread.
//
DmpShutdownFlusher();
return(dwError);
}
DWORD
DmpStartFlusher(
VOID
)
/*++
Routine Description:
Starts up a new registry flusher thread.
Arguments:
None.
Return Value:
ERROR_SUCCESS if successful
Win32 error code otherwise
--*/
{
DWORD ThreadId;
ClRtlLogPrint(LOG_NOISE,"[DM] DmpStartFlusher: Entry\r\n");
if (!hDmpRegistryFlusher)
{
hDmpRegistryEvent = CreateEventW(NULL,FALSE,FALSE,NULL);
if (hDmpRegistryEvent == NULL) {
return(GetLastError());
}
hDmpRegistryRestart = CreateEventW(NULL,FALSE,FALSE,NULL);
if (hDmpRegistryRestart == NULL) {
CloseHandle(hDmpRegistryEvent);
return(GetLastError());
}
hDmpRegistryFlusher = CreateThread(NULL,
0,
DmpRegistryFlusher,
NULL,
0,
&ThreadId);
if (hDmpRegistryFlusher == NULL) {
CloseHandle(hDmpRegistryRestart);
CloseHandle(hDmpRegistryEvent);
return(GetLastError());
}
ClRtlLogPrint(LOG_NOISE,"[DM] DmpStartFlusher: thread created\r\n");
}
return(ERROR_SUCCESS);
}
VOID
DmpShutdownFlusher(
VOID
)
/*++
Routine Description:
Cleanly shutsdown the registry flusher thread.
Arguments:
None.
Return Value:
None.
--*/
{
ClRtlLogPrint(LOG_NOISE,"[DM] DmpShutdownFlusher: Entry\r\n");
if (hDmpRegistryFlusher) {
ClRtlLogPrint(LOG_NOISE,"[DM] DmpShutdownFlusher: Setting event\r\n");
SetEvent(hDmpRegistryEvent);
WaitForSingleObject(hDmpRegistryFlusher, INFINITE);
CloseHandle(hDmpRegistryFlusher);
hDmpRegistryFlusher = NULL;
CloseHandle(hDmpRegistryEvent);
CloseHandle(hDmpRegistryRestart);
hDmpRegistryEvent = NULL;
hDmpRegistryRestart = NULL;
}
}
VOID
DmpRestartFlusher(
VOID
)
/*++
Routine Description:
Restarts the registry flusher thread if DmpRoot is being changed.
N.B. In order for this to work correctly, gLockDmpRoot MUST be held!
Arguments:
None.
Return Value:
None.
--*/
{
ClRtlLogPrint(LOG_NOISE,"[DM] DmpRestartFlusher: Entry\r\n");
#if NO_SHARED_LOCKS
CL_ASSERT(HandleToUlong(gLockDmpRoot.OwningThread) == GetCurrentThreadId());
#else
CL_ASSERT(HandleToUlong(gLockDmpRoot.ExclusiveOwnerThread) == GetCurrentThreadId());
#endif
if (hDmpRegistryRestart) {
// GorN 11/11/2001 DmpRestart flusher could be called before the hDmpRegistryRestart is set
SetEvent(hDmpRegistryRestart);
}
}
DWORD
DmUpdateJoinCluster(
VOID
)
/*++
Routine Description:
This routine is called after a node has successfully joined a cluster.
It allows the DM to hook callbacks for node up/down notifications and for
quorum resource change notification.
Arguments:
None.
Return Value:
ERROR_SUCCESS if successful.
A Win32 error code on failure.
--*/
{
DWORD dwError=ERROR_SUCCESS;
BOOL bAreAllNodesUp = FALSE;
ClRtlLogPrint(LOG_NOISE,
"[DM] DmUpdateJoinCluster: Begin.\r\n");
//if all nodes are not up, turn quorum logging on
if ((dwError = OmEnumObjects(ObjectTypeNode, DmpNodeObjEnumCb, &bAreAllNodesUp, NULL))
!= ERROR_SUCCESS)
{
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmUpdateJoinCluster : OmEnumObjects returned 0x%1!08lx!\r\n",
dwError);
goto FnExit;
}
if (!bAreAllNodesUp)
{
ClRtlLogPrint(LOG_NOISE,
"[DM] DmUpdateJoinCluster - some node down\n");
gbIsQuoLoggingOn = TRUE;
}
//hook the notification for node up/down so we can keep track of whether logging
//should be on or off.
if (dwError = DmpHookEventHandler())
{
//BUGBUG SS: do we log this or return this error code
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmUpdateJoinCluster: DmpHookEventHandler failed 0x%1!08lx!\r\n",
dwError);
}
//hook the callback for notification on quorum resource
if (dwError = DmpHookQuorumNotify())
{
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmUpdateJoinCluster: DmpHookQuorumNotify failed 0x%1!08lx!\r\n",
dwError);
goto FnExit;
}
if ((dwError = DmpCheckDiskSpace()) != ERROR_SUCCESS)
{
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmUpdateJoinCluster: DmpCheckDiskSpace() failed 0x%1!08lx!\r\n",
dwError);
goto FnExit;
}
//add a timer to monitor disk space, should be done after we have joined.
ghDiskManTimer = CreateWaitableTimer(NULL, FALSE, NULL);
if (!ghDiskManTimer)
{
CL_LOGFAILURE(dwError = GetLastError());
goto FnExit;
}
//register a periodic timer
AddTimerActivity(ghDiskManTimer, DISKSPACE_MANAGE_INTERVAL, 1, DmpDiskManage, NULL);
gbDmInited = TRUE;
FnExit:
return(dwError);
} // DmUpdateJoinCluster
DWORD
DmpOpenKeys(
IN REGSAM samDesired
)
/*++
Routine Description:
Opens all the standard cluster registry keys. If any of the
keys are already opened, they will be closed and reopened.
Arguments:
samDesired - Supplies the access that the keys will be opened with.
Return Value:
ERROR_SUCCESS if successful.
Win32 error code otherwise.
--*/
{
DWORD i;
DWORD Status;
DmClusterParametersKey = DmGetRootKey( MAXIMUM_ALLOWED );
if ( DmClusterParametersKey == NULL ) {
Status = GetLastError();
CL_UNEXPECTED_ERROR(Status);
return(Status);
}
for (i=0;
i<sizeof(DmpKeyTable)/sizeof(DMP_KEY_DEF);
i++) {
*DmpKeyTable[i].pKey = DmOpenKey(DmClusterParametersKey,
DmpKeyTable[i].Name,
samDesired);
if (*DmpKeyTable[i].pKey == NULL) {
Status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[DM] Failed to open key %1!ws!, status %2!u!\n",
DmpKeyTable[i].Name,
Status);
CL_UNEXPECTED_ERROR( Status );
return(Status);
}
}
return(ERROR_SUCCESS);
}
VOID
DmpInvalidateKeys(
VOID
)
/*++
Routine Description:
Invalidates all open cluster registry keys.
Arguments:
None.
Return Value:
None.
--*/
{
PLIST_ENTRY ListEntry;
PDMKEY Key;
ListEntry = KeyList.Flink;
while (ListEntry != &KeyList) {
Key = CONTAINING_RECORD(ListEntry,
DMKEY,
ListEntry);
if (!Key->hKey)
{
ClRtlLogPrint(LOG_CRITICAL,
"[DM] DmpInvalidateKeys %1!ws! Key was deleted since last reopen but not closed\n",
Key->Name);
ClRtlLogPrint(LOG_CRITICAL,
"[DM] THIS MAY BE A KEY LEAK !!\r\n");
}
else
{
RegCloseKey(Key->hKey);
Key->hKey = NULL;
}
ListEntry = ListEntry->Flink;
}
}
VOID
DmpReopenKeys(
VOID
)
/*++
Routine Description:
Reopens all the keys that were invalidated by DmpInvalidateKeys
Arguments:
None
Return Value:
None.
--*/
{
PLIST_ENTRY ListEntry;
PDMKEY Key;
DWORD Status;
ListEntry = KeyList.Flink;
while (ListEntry != &KeyList) {
Key = CONTAINING_RECORD(ListEntry,
DMKEY,
ListEntry);
CL_ASSERT(Key->hKey == NULL);
Status = RegOpenKeyEx(DmpRoot,
Key->Name,
0,
Key->GrantedAccess,
&Key->hKey);
if (Status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL,"[DM] Could not reopen key %1!ws! error %2!d!\n",Key->Name,Status);
// if the error is file not found, then the key was deleted while the handle
// was open. Set the key to NULL
// If the key is used after delete, it should be validated
if (Status == ERROR_FILE_NOT_FOUND)
Key->hKey = NULL;
else
CL_UNEXPECTED_ERROR(Status);
}
ListEntry = ListEntry->Flink;
}
}
DWORD
DmpGetRegistrySequence(
VOID
)
/*++
Routine Description:
Returns the current registry sequence stored in the registry.
Arguments:
None.
Return Value:
The current registry sequence.
--*/
{
DWORD Length;
DWORD Type;
DWORD Sequence;
DWORD Status;
Length = sizeof(Sequence);
Status = RegQueryValueExW(DmpRoot,
CLUSREG_NAME_CLUS_REG_SEQUENCE,
0,
&Type,
(LPBYTE)&Sequence,
&Length);
if (Status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_UNUSUAL, "[DM] DmpGetRegistrySequence failed %1!u!\n",Status);
Sequence = 0;
}
return(Sequence);
}
DWORD DmWaitQuorumResOnline()
/*++
Routine Description:
Waits for quorum resource to come online. Used for quorum logging.
Arguments:
None
Return Value:
returns ERROR_SUCCESS - if the online event is signaled and the quorum
notification callback is called. Else returns the wait status.
--*/
{
// Wait indefinitely for the quorum resource to go online
DWORD dwError = ERROR_INVALID_PARAMETER;
if (ghQuoLogOpenEvent)
{
dwError = WaitForSingleObject(ghQuoLogOpenEvent, INFINITE);
switch(dwError)
{
case WAIT_OBJECT_0:
//everything is fine
dwError = ERROR_SUCCESS;
break;
case WAIT_TIMEOUT:
//couldnt roll the changes
dwError = ERROR_TIMEOUT;
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmRollChanges: Timed out waiting on dmInitEvent\r\n");
break;
case WAIT_FAILED:
CL_ASSERT(dwError != WAIT_FAILED);
dwError = GetLastError();
ClRtlLogPrint(LOG_UNUSUAL,
"[DM] DmRollChanges: wait on dmInitEventfailed failed 0x%1!08lx!\r\n",
dwError );
break;
} // switch
}
return(dwError);
}
VOID DmShutdownUpdates(
VOID
)
/*++
Routine Description:
Shutdown DM GUM updates.
Arguments:
None
Return Value:
None.
--*/
{
gbDmpShutdownUpdates = TRUE;
}