Source code of Windows XP (NT5)
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.
 
 
 
 
 
 

1490 lines
40 KiB

/*++
Copyright (c) 1995-1999 Microsoft Corporation
Module Name:
service.c
Abstract:
Service control functions for the Cluster Service.
Author:
Mike Massa (mikemas) 2-Jan-1996
Revision History:
--*/
#include <initp.h>
#include <shellapi.h>
#include <stdio.h>
#include <stdlib.h>
#include <clusverp.h>
//
// Public data
//
#define CLUSTER_SERVICE_CONTROLS (SERVICE_ACCEPT_STOP | \
SERVICE_ACCEPT_SHUTDOWN )
ULONG CsLogLevel=LOG_UNUSUAL;
PCLRTL_WORK_QUEUE CsDelayedWorkQueue = NULL;
PCLRTL_WORK_QUEUE CsCriticalWorkQueue = NULL;
LPWSTR CsClusterName = NULL;
SERVICE_STATUS CsServiceStatus = {
SERVICE_WIN32_OWN_PROCESS, // dwServiceType
SERVICE_STOPPED, // dwCurrentState
CLUSTER_SERVICE_CONTROLS, // dwControlsAccepted
ERROR_SUCCESS, // dwWin32ExitCode
ERROR_SUCCESS, // dwServiceSpecificExitCode
1, // dwCheckPoint
90000 // dwWaitHint - 90 seconds -nm uses 90 sec timeout
};
//
// internal cluster versions. The major version is bumped during
// product releases (which could include service pack releases).
//
DWORD CsMyHighestVersion = CLUSTER_MAKE_VERSION(
CLUSTER_INTERNAL_CURRENT_MAJOR_VERSION,
VER_PRODUCTBUILD);
DWORD CsMyLowestVersion = CLUSTER_INTERNAL_PREVIOUS_HIGHEST_VERSION;
//initialize by calling an rtl funcion
SUITE_TYPE CsMyProductSuite;
DWORD CsClusterHighestVersion;
DWORD CsClusterLowestVersion;
DWORD CsClusterNodeLimit;
SHUTDOWN_TYPE CsShutdownRequest = CsShutdownTypeStop;
//
// used to turn authenticated RPC on or off
//
// This should always be defined, since none of the other code has
// the checks for this variable conditionalized
//
BOOL CsUseAuthenticatedRPC = TRUE;
//
// domain and user account under which the service is run
//
LPWSTR CsServiceDomainAccount;
//
// security packages to use during the join for authenticated RPC; JoinVersion
// determines which package will be used by the ExtroCluster interface.
// CsRPCSecurityPackageInUse reflects that choice. The package used for the
// Intracluster interface is negotiated separately.
//
//
// when using kerberos with RPC, RPC calls fail with 1825 (sec. pkg error)
// somewhere between 30 minutes and 12 hours. For beta 2, we'll revert back to
// NTLM where expiration is not a problem.
//
//DWORD CsRPCSecurityPackage[] = { RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_WINNT };
//LPWSTR CsRPCSecurityPackageName[] = { L"Kerberos", L"NTLM" };
DWORD CsRPCSecurityPackage[] = { RPC_C_AUTHN_WINNT };
LPWSTR CsRPCSecurityPackageName[] = { L"NTLM" };
DWORD CsNumberOfRPCSecurityPackages = sizeof( CsRPCSecurityPackage ) / sizeof( CsRPCSecurityPackage[0] );
LONG CsRPCSecurityPackageIndex = -1;
//
// Public Debug Data
//
#if 1 // CLUSTER_BETA
BOOL CsDebugResmon = FALSE;
LPWSTR CsResmonDebugCmd;
BOOL CsNoVersionCheck = FALSE;
#endif
#if DBG // DBG
ULONG CsDebugFlags = CS_DBG_ALL;
#endif // DBG
#ifdef CLUSTER_TESTPOINT
DWORD CsTestPoint = 0;
DWORD CsTestTrigger = TestTriggerNever;
DWORD CsTestAction = TestActionTrue;
BOOL CsPersistentTestPoint = FALSE;
#endif // CLUSTER_TESTPOINT
BOOL CsUpgrade = FALSE;
BOOL CsFirstRun = FALSE;
BOOL CsNoQuorumLogging = FALSE;
BOOL CsUserTurnedOffQuorumLogging = FALSE;
BOOL CsNoQuorum = FALSE;
BOOL CsResetQuorumLog = FALSE;
BOOL CsForceQuorum = FALSE;
LPWSTR CsForceQuorumNodes = NULL;
BOOL CsCommandLineForceQuorum = FALSE;
BOOL CsNoRepEvtLogging = FALSE;
LPWSTR CsDatabaseRestorePath = NULL;
BOOL CsDatabaseRestore = FALSE;
BOOL CsForceDatabaseRestore = FALSE;
LPWSTR CsQuorumDriveLetter = NULL;
DWORD CspInitStatus;
BOOL CsRunningAsService = TRUE;
//
// Private Data
//
SERVICE_STATUS_HANDLE CspServiceStatusHandle = 0;
HANDLE CspStopEvent = NULL;
//
// Private service initialization & cleanup routines.
//
DWORD
CspSetErrorCode(
IN DWORD ErrorCode,
OUT LPSERVICE_STATUS ServiceStatus
)
/*++
Routine Description:
Sets the correct error return for the Service Control Manager.
Problem:
The original cluster error codes overlap with many of the network error
codes. For those overlaps, this function will return the error code as a
service specific error code.
Inputs:
EerrorCode - the correct error code to set.
ServiceStatus - pointer to the service status for SCM
Outputs:
ServiceStatus - Sets the correct error code in the service status.
--*/
{
DWORD status;
if ( ( ErrorCode > 5000 ) && ( ErrorCode < 5090 ) ) {
ServiceStatus->dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
ServiceStatus->dwServiceSpecificExitCode = ErrorCode;
status = ERROR_SERVICE_SPECIFIC_ERROR;
} else {
ServiceStatus->dwWin32ExitCode = ErrorCode;
ServiceStatus->dwServiceSpecificExitCode = ErrorCode;
status = ErrorCode;
}
return (status);
} // CspSetErrorCode
VOID
CspCleanup(
VOID
)
/*++
Routine Description:
Main Cluster Manager cleanup routine. Called when the service is
stopping.
Arguments:
None.
Return Value:
None.
--*/
{
//
// Cleanup & shutdown the service
//
IF_DEBUG(CLEANUP) {
ClRtlLogPrint(LOG_NOISE,"[CS] Cleaning up\n");
}
//
// Free the stop event
//
if (CspStopEvent != NULL) {
CloseHandle(CspStopEvent);
CspStopEvent = NULL;
}
CsServiceStatus.dwCheckPoint++;
CsAnnounceServiceStatus();
if ( CsDatabaseRestorePath != NULL ) {
LocalFree ( CsDatabaseRestorePath );
}
if ( CsQuorumDriveLetter != NULL ) {
LocalFree ( CsQuorumDriveLetter );
}
if ( CsForceQuorumNodes != NULL && !CsCommandLineForceQuorum ) {
LocalFree ( CsForceQuorumNodes );
}
IF_DEBUG(CLEANUP) {
ClRtlLogPrint(LOG_NOISE,"[CS] Cleanup complete.\n");
}
return;
} // CspCleanup
//
// Public service control routines.
//
VOID
CsWaitForStopEvent(
VOID
)
/*++
Routine Description:
Main body of the Cluster Manager service. Called when the service
has been successfully started.
Arguments:
None.
Return Value:
A Win32 status code.
--*/
{
DWORD status;
CL_ASSERT(CsRunningAsService);
if (CsRunningAsService) {
//
// Announce that we're up and running.
//
CsServiceStatus.dwCurrentState = SERVICE_RUNNING;
CsServiceStatus.dwControlsAccepted = CLUSTER_SERVICE_CONTROLS;
CsServiceStatus.dwCheckPoint = 0;
CsServiceStatus.dwWaitHint = 0;
CsAnnounceServiceStatus();
}
IF_DEBUG(INIT) {
ClRtlLogPrint(LOG_NOISE,"[CS] Service Started.\n\n");
}
//
// Wait for the service to be stopped.
//
WaitForSingleObject(CspStopEvent, // handle
INFINITE // no timeout
);
return;
} // CsWaitForStopEvent
VOID
CsStopService(
VOID
)
/*++
Routine Description:
Handler for a service controller STOP message. Initiates the process
of stopping the Cluster Manager service.
Arguments:
None.
Return Value:
None.
--*/
{
if (CsRunningAsService) {
//
// Announce that we are stopping.
//
CsServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
CsServiceStatus.dwCheckPoint = 1;
CsServiceStatus.dwWaitHint = 20000; // 20 seconds
CsAnnounceServiceStatus();
}
//
// Wake up the main service thread.
//
SetEvent(CspStopEvent);
return;
}
VOID
CsAnnounceServiceStatus (
VOID
)
/*++
Routine Description:
Announces the service's status to the service controller.
Arguments:
None.
Return Value:
None.
--*/
{
//
// Don't announce our status if running as a console app.
//
if (!CsRunningAsService) {
return;
}
//
// Service status handle is NULL if RegisterServiceCtrlHandler failed.
//
if ( CspServiceStatusHandle == 0 ) {
return;
}
//
// Call SetServiceStatus, ignoring any errors.
//
SetServiceStatus(CspServiceStatusHandle, &CsServiceStatus);
return;
} // CsAnnounceServiceStatus
//
// Private routines for executing as a Win32 service.
//
VOID WINAPI
CspControlHandler(
DWORD ControlCode
)
/*++
Routine Description:
Handler for Service Controller messages.
Arguments:
ControlCode - The code indicating the Service Controller's request.
Return Value:
None.
--*/
{
switch(ControlCode){
case SERVICE_CONTROL_SHUTDOWN:
CsShutdownRequest = CsShutdownTypeShutdown;
// Fall Through
case SERVICE_CONTROL_STOP:
IF_DEBUG(CLEANUP) {
ClRtlLogPrint(LOG_NOISE,
"[CS] Received %1!ws! command\n",
(ControlCode == SERVICE_CONTROL_STOP ? L"STOP" : L"SHUTDOWN"));
}
CsStopService();
break;
case SERVICE_CONTROL_INTERROGATE:
CsAnnounceServiceStatus();
break;
case SERVICE_CONTROL_CONTINUE:
case SERVICE_CONTROL_PAUSE:
break;
default:
ClRtlLogPrint(LOG_NOISE,
"[CS] Received unknown service command %1!u!\n",
ControlCode
);
break;
}
return;
} // CspControlHandler
DWORD CspGetFirstRunState(
OUT LPDWORD pdwFirstRun
)
{
HKEY hKey = NULL;
DWORD dwStatus; // returned by registry API functions
DWORD dwClusterInstallState;
DWORD dwValueType;
DWORD dwDataBufferSize = sizeof( DWORD );
*pdwFirstRun = 0;
// Read the registry key that indicates whether cluster files are installed.
dwStatus = RegOpenKeyExW( HKEY_LOCAL_MACHINE,
L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Cluster Server",
0, // reserved
KEY_READ,
&hKey );
// Was the registry key opened successfully ?
if ( dwStatus != ERROR_SUCCESS )
{
if ( dwStatus == ERROR_FILE_NOT_FOUND )
{
*pdwFirstRun = 1;
dwStatus = ERROR_SUCCESS;
goto FnExit;
}
}
// Read the entry.
dwStatus = RegQueryValueExW( hKey,
L"ClusterFirstRun",
0, // reserved
&dwValueType,
(LPBYTE) pdwFirstRun,
&dwDataBufferSize );
// Was the value read successfully ?
if ( dwStatus != ERROR_SUCCESS )
{
if ( dwStatus == ERROR_FILE_NOT_FOUND )
{
*pdwFirstRun = 1;
dwStatus = ERROR_SUCCESS;
goto FnExit;
}
}
FnExit:
// Close the registry key.
if ( hKey )
{
RegCloseKey( hKey );
}
return ( dwStatus );
} //*** CspGetFirstRunState
DWORD CspGetServiceParams()
{
HKEY hClusSvcKey = NULL;
DWORD Length;
DWORD Type;
DWORD Status;
eClusterInstallState eState;
//
// Open key to SYSTEM\CurrentControlSet\Services\ClusSvc\Parameters
//
Status = RegOpenKeyW(HKEY_LOCAL_MACHINE,
CLUSREG_KEYNAME_CLUSSVC_PARAMETERS,
&hClusSvcKey);
Length = sizeof(DWORD);
Status = RegQueryValueExW(hClusSvcKey,
CLUSREG_NAME_SVC_PARAM_NOVER_CHECK,
0,
&Type,
(LPBYTE)&CsNoVersionCheck,
&Length);
// by default, version checking is turned on
if (Status != ERROR_SUCCESS) {
CsNoVersionCheck = FALSE;
Status = ERROR_SUCCESS;
}
Length = sizeof(DWORD);
Status = RegQueryValueExW(hClusSvcKey,
CLUSREG_NAME_SVC_PARAM_NOREP_EVTLOGGING,
0,
&Type,
(LPBYTE)&CsNoRepEvtLogging,
&Length);
//For now, default is to turn eventlogging on
if (Status != ERROR_SUCCESS) {
CsNoRepEvtLogging = FALSE;
Status = ERROR_SUCCESS;
}
//figure out if this is the first run on upgrade or fresh install
Status = CspGetFirstRunState((LPDWORD)&CsFirstRun);
CL_ASSERT(Status == ERROR_SUCCESS);
//if there is upgrade, this must be the first run
Status = ClRtlGetClusterInstallState( NULL, &eState );
if (eState == eClusterInstallStateUpgraded)
{
CsUpgrade = TRUE;
CsFirstRun = TRUE;
}
//
// Check the registry to see whether RestoreDatabase option is
// chosen. If so, get the params and save them in global variables.
//
RdbGetRestoreDbParams( hClusSvcKey );
//
// See if the force quorum option has been set. Unfortunately we
// need two calls to get the size and do the alloc. Note that if
// we have command line stuff already then this overrides registry
// parameters. If we have command line stuff then CsForceQuorum
// will be set. Care is needed since we could be unlucky with the
// time between the two calls.
//
if ( !CsForceQuorum ) {
GetForceQuorum:
Length = 0;
Status = RegQueryValueExW( hClusSvcKey,
CLUSREG_NAME_SVC_PARAM_FORCE_QUORUM,
0,
&Type,
NULL,
&Length);
if (Status == ERROR_SUCCESS) {
// Got the length, check the type before allocating
//
if ( Type != REG_SZ ) {
ClRtlLogPrint( LOG_ERROR,"[CS] Error in startup param, type was not REG_SZ.\r\n" );
Status = ERROR_INVALID_PARAMETER;
goto ret;
}
// Got a valid type so force quorum is set, check the length.
// If the length is 0 then we have the key but no data which
// is OK. Otherwise alloc and read the data.
//
CsForceQuorum = TRUE;
if ( Length == 0 )
goto ret;
CsForceQuorumNodes = (LPWSTR) LocalAlloc( LMEM_FIXED, Length );
Status = RegQueryValueExW( hClusSvcKey,
CLUSREG_NAME_SVC_PARAM_FORCE_QUORUM,
0,
&Type,
(LPBYTE) CsForceQuorumNodes,
&Length);
if ( Status == ERROR_MORE_DATA || Type != REG_SZ ) {
LocalFree( CsForceQuorumNodes );
CsForceQuorumNodes = NULL;
CsForceQuorum = FALSE;
goto GetForceQuorum;
}
if ( Status != ERROR_SUCCESS )
goto ret;
} else {
Status = ERROR_SUCCESS;
}
}
ret:
//close the key
if (hClusSvcKey) RegCloseKey(hClusSvcKey);
return(Status);
}
BOOL CspResetFirstRunState(DWORD dwFirstRunState)
{
//initialize return to FALSE
BOOL fReturnValue = FALSE;
// Set the state of the ClusterInstallationState registry key to indicate
// that Cluster Server has been configured.
HKEY hKey;
DWORD dwStatus; // returned by registry API functions
// Attempt to open an existing key in the registry.
dwStatus = RegOpenKeyExW( HKEY_LOCAL_MACHINE,
L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Cluster Server",
0, // reserved
KEY_WRITE,
&hKey );
// Was the regustry key opened successfully ?
if ( dwStatus == ERROR_SUCCESS )
{
// set the first run state to 0.
DWORD dwFirstRun = 0;
DWORD dwValueType = REG_DWORD;
DWORD dwDataBufferSize = sizeof( DWORD );
dwStatus = RegSetValueExW( hKey,
L"ClusterFirstRun",
0, // reserved
dwValueType,
(LPBYTE) &dwFirstRun,
dwDataBufferSize );
// Close the registry key.
RegCloseKey( hKey );
// Was the value set successfully?
if ( dwStatus == ERROR_SUCCESS )
{
fReturnValue = TRUE;
}
}
return ( fReturnValue );
} //*** CspResetFirstRunState
DWORD
CspSetInstallAndFirstRunState(
VOID
)
/*++
Routine Description:
Sets the cluster state to Configured. Called
after the service has started running after the first upgrade.
If it is a fresh install, Cluscfg sets the state of this to
Configured before starting the cluster service
Arguments:
None
Return Value:
ERROR_SUCCESS if everything worked ok
--*/
{
DWORD Status = ERROR_SUCCESS;
if (CsUpgrade)
{
if (!ClRtlSetClusterInstallState(eClusterInstallStateConfigured))
{
Status = GetLastError();
}
}
if (CsFirstRun)
{
CspResetFirstRunState(0);
}
return(Status);
} // CspResetUpgradeBit
void CspGetServiceCmdLineParams(
DWORD argc,
LPTSTR argv[]
)
{
DWORD i;
if ((argc > 1) && ((*argv[1] == '-') || (*argv[1] == '/')))
{
for (i=1; i<argc; i++)
{
if (!lstrcmpi(argv[i]+1,L"noquorumlogging"))
{
CsNoQuorumLogging = TRUE;
CsUserTurnedOffQuorumLogging = TRUE;
ClRtlLogPrint(LOG_NOISE,"[CS] quorum logging is off\r\n");
}
else if (!lstrcmpi(argv[i]+1,L"fixquorum"))
{
CsNoQuorum = TRUE;
CsNoQuorumLogging = TRUE;
CsUserTurnedOffQuorumLogging = TRUE;
ClRtlLogPrint(LOG_NOISE,
"[CS] quorum is not arbitrated or brought online\r\n");
}
else if (!lstrcmpi(argv[i]+1,L"resetquorumlog"))
{
CsResetQuorumLog = TRUE;
ClRtlLogPrint(LOG_NOISE,
"[CS] force reset quorum log\r\n");
}
else if (!lstrcmpi(argv[i]+1,L"forcequorum"))
{
CsForceQuorum = TRUE;
CsCommandLineForceQuorum = TRUE;
if (( argc < i+2 )
|| ( *argv[i+1] == L'-' )
|| ( *argv[i+1] == L'/' )) {
CsForceQuorumNodes = NULL;
} else {
CsForceQuorumNodes = argv[++i]; /* increment i to ensure we skip the node list. */
}
ClRtlLogPrint(LOG_NOISE, "[CS] force node quorum for nodes %1\r\n", CsForceQuorumNodes);
}
else if ( lstrcmpiW( L"debugresmon", argv[i]+1 ) == 0 ) {
CsDebugResmon = TRUE;
//
// check for optional, non-NULL command string
//
if ( argc >= i+2 ) {
if ( *argv[i+1] != L'-' && *argv[i+1] != L'/' && *argv[i+1] != UNICODE_NULL ) {
CsResmonDebugCmd = argv[++i];
}
}
}
}
}
}
VOID WINAPI
CspServiceMain(
DWORD argc,
LPTSTR argv[]
)
{
DWORD status;
ClRtlLogPrint(LOG_NOISE,"[CS] Service Starting...\n");
if ( CspInitStatus == ERROR_SUCCESS ) {
CsServiceStatus.dwCurrentState = SERVICE_START_PENDING;
} else {
CsServiceStatus.dwCurrentState = SERVICE_STOPPED;
CsServiceStatus.dwWin32ExitCode = CspInitStatus;
}
//
// Initialize server to receive service requests by registering the
// control handler.
//
CspServiceStatusHandle = RegisterServiceCtrlHandler(
CLUSTER_SERVICE_NAME,
CspControlHandler
);
if ( CspServiceStatusHandle == 0 ) {
status = GetLastError();
ClRtlLogPrint(LOG_NOISE,"[CS] Service Registration failed, %1!u!\n", status);
CL_UNEXPECTED_ERROR( status );
return;
}
IF_DEBUG(INIT) {
ClRtlLogPrint(LOG_NOISE,"[CS] Service control handler registered\n");
}
CsAnnounceServiceStatus();
if ( CspInitStatus != ERROR_SUCCESS ) {
return;
}
CspGetServiceCmdLineParams(argc, argv);
//
// Initialize the cluster. If this succeeds, wait for
// the SC mgr to stop us
//
status = ClusterInitialize();
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL, "[CS] ClusterInitialize failed %1!d!\n",
status);
} else {
CspSetInstallAndFirstRunState();
CsWaitForStopEvent();
}
//
// Announce that we're stopping
//
IF_DEBUG(CLEANUP) {
ClRtlLogPrint(LOG_NOISE,"[CS] Service Stopping...\n");
}
CsServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
CsServiceStatus.dwCheckPoint = 1;
CsServiceStatus.dwWaitHint = 20000; // 20 seconds
CspSetErrorCode( status, &CsServiceStatus );
CsAnnounceServiceStatus();
//
// ClusterShutdown currently never returns
//
ClusterShutdown(status);
#if 0
CspCleanup();
//
// Announce that we are stopped.
//
CsServiceStatus.dwCurrentState = SERVICE_STOPPED;
CsServiceStatus.dwControlsAccepted = 0;
CsServiceStatus.dwCheckPoint = 0;
CsServiceStatus.dwWaitHint = 0;
CspSetErrorCode( status, &CsServiceStatus );
CsAnnounceServiceStatus();
ClRtlLogPrint(LOG_NOISE,"[CS] Service Stopped.\n\n");
//
// Can't call ClRtlLogPrint after this point.
//
ClRtlCleanup();
return;
#endif
} // CspServiceMain
//
// Private routines for executing as a console application.
//
BOOL WINAPI
CspConsoleHandler(
DWORD dwCtrlType
)
/*++
Routine Description:
Handler for console control events when running the service as
a console application.
Arguments:
dwCtrlType - Indicates the console event to handle.
Return Value:
TRUE if the event was handled, FALSE otherwise.
--*/
{
switch( dwCtrlType )
{
case CTRL_BREAK_EVENT: // use Ctrl+C or Ctrl+Break to simulate
case CTRL_C_EVENT: // SERVICE_CONTROL_STOP in debug mode
printf("Stopping service...\n");
CsStopService();
return TRUE;
break;
}
return FALSE;
}
DWORD
CspDebugService(
int argc,
wchar_t ** argv
)
/*++
Routine Description:
Runs the service as a console application
Arguments:
Standard command-line arguments.
Return Value:
None.
--*/
{
DWORD status;
SetConsoleCtrlHandler( CspConsoleHandler, TRUE );
status = ClusterInitialize();
if (status == ERROR_SUCCESS) {
CspSetInstallAndFirstRunState();
//
// Wait for ctrl-c to initiate shutdown.
//
WaitForSingleObject(CspStopEvent, INFINITE);
} else {
ClRtlLogPrint(LOG_CRITICAL,
"[CS] ClusterInitialize failed %1!d!\n",
status);
}
ClusterShutdown(status);
CspCleanup();
//
// Can't call ClRtlLogPrint after this point.
//
ClRtlCleanup();
return(status);
}
//
// Main program routines
//
VOID
CspUsage(
VOID
)
{
#if DBG
printf("\nCluster Service\n");
printf("\n");
printf("Start with 'net start' to run as a Win32 service\n");
printf("\n");
printf("Command line options:\n");
printf("\t-loglevel N set the debugging log level.\n");
printf("\t-debug run as a console app.\n");
printf("\t-debugresmon [dbgcmd] enable debugging of resrcmon process using optional command.\n");
printf("\t use quotes to include args, i.e., -debugresmon \"ntsd -d\"\n");
printf("\t-fixquorum no quorum device, no quorum logging.\n");
printf("\t-noquorumlogging no quorum logging.\n");
printf("\t-forcequorum N1,...,Nn force a quorum of nodes for node N1 up to Nn inclusive.\n");
printf("\t-restoredatabase D restore cluster DB to quorum disk from dir D.\n");
printf("\t-forcerestore force a restore DB operation by performing fixups.\n");
printf("\t-resetquorumlog force a form despite a missing quorum log file.\n");
printf("\t-quodriveletter Q drive letter for a replacement quorum disk\n");
printf("\t-norepevtlogging no replication of event log entries.\n");
printf("\t-novercheck ignore join version checking.\n");
printf("\t-testpt N enable test point N.\n");
printf("\t-persistent make test points persistent.\n");
printf("\t-trigger N sets test point trigger type.\n");
printf("\t (0-never (default), 1-always, 2-once, 3-count)\n");
printf("\t-action N sets trigger action.\n");
printf("\t (0-true (default), 1-exit, 2-break)\n");
printf("\n");
#else // DBG
ClRtlMsgPrint(CS_COMMAND_LINE_HELP);
#endif // DBG
exit(1);
}
int __cdecl
wmain(
int argc,
wchar_t **argv
)
{
DWORD Status;
int i;
LPWSTR LogLevel;
BOOLEAN debugFlagFound = FALSE;
OSVERSIONINFOEXW Version;
DWORD dwLen;
BOOL success;
PWCHAR suiteInfo;
SYSTEMTIME localTime;
BOOLEAN dbgOutputToConsole;
UINT errorMode;
DWORD dwMask;
SERVICE_TABLE_ENTRY dispatchTable[] = {
{ CLUSTER_SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)CspServiceMain },
{ NULL, NULL }
};
//
// BUGBUG - 06/23/2000
//
// This is a temporary change to let the cluster service and resource monitor process run
// despite 64-bit alignment faults. This will be removed as soon as all alignment issues
// are fixed.
//
errorMode = SetErrorMode( SEM_NOALIGNMENTFAULTEXCEPT );
SetErrorMode( SEM_NOALIGNMENTFAULTEXCEPT | errorMode );
LogLevel = _wgetenv(L"ClusterLogLevel");
if (LogLevel != NULL) {
swscanf(LogLevel, L"%u", &CsLogLevel);
}
if ( (argc > 1) && ((*argv[1] == L'-') || (*argv[1] == L'/')) ) {
//
// Invoked from the command line.
//
CsRunningAsService = FALSE;
dbgOutputToConsole = TRUE;
} else {
//
// Invoked by the Service Controller
//
CsRunningAsService = TRUE;
dbgOutputToConsole = FALSE;
}
//
// initialize the run time library
//
Status = ClRtlInitialize( dbgOutputToConsole, &CsLogLevel );
if (Status != ERROR_SUCCESS) {
if (Status == ERROR_PATH_NOT_FOUND) {
CsLogEvent( LOG_CRITICAL, SERVICE_CLUSRTL_BAD_PATH );
} else {
PWCHAR msgString;
DWORD msgStatus;
msgStatus = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
Status,
0,
(LPWSTR)&msgString,
0,
NULL);
if ( msgStatus != 0 ) {
CsLogEventData1(LOG_CRITICAL,
SERVICE_CLUSRTL_ERROR,
sizeof(Status),
(PVOID)&Status,
msgString);
LocalFree( msgString);
}
}
goto init_failed;
}
ClRtlInitWmi(L"Clustering Service");
//
// Log the version number
//
ClRtlLogPrint( LOG_NOISE, "\n\n");
ClRtlLogPrint( LOG_NOISE,
"[CS] Cluster Service started - Cluster Node Version %1!u!.%2!u!\n",
CLUSTER_GET_MAJOR_VERSION( CsMyHighestVersion ),
CLUSTER_GET_MINOR_VERSION( CsMyHighestVersion ));
Version.dwOSVersionInfoSize = sizeof(Version);
success = GetVersionExW((POSVERSIONINFOW)&Version);
if ( success ) {
//
// Log the System version number
//
if ( Version.wSuiteMask & VER_SUITE_DATACENTER ) {
suiteInfo = L"DTC";
} else if ( Version.wSuiteMask & VER_SUITE_ENTERPRISE ) {
suiteInfo = L"ADS";
} else if ( Version.wSuiteMask & VER_SUITE_EMBEDDEDNT ) {
suiteInfo = L"EMB";
} else if ( Version.wProductType & VER_NT_WORKSTATION ) {
suiteInfo = L"WS";
} else if ( Version.wProductType & VER_NT_DOMAIN_CONTROLLER ) {
suiteInfo = L"DC";
} else if ( Version.wProductType & VER_NT_SERVER ) {
suiteInfo = L"SRV"; // otherwise - some non-descript Server
} else {
suiteInfo = L"";
}
dwMask = (Version.wProductType << 24) | Version.wSuiteMask;
ClRtlLogPrint(LOG_NOISE,
" OS Version %1!u!.%2!u!.%3!u!%4!ws!%5!ws! (%6!ws! %7!08XL!)\n",
Version.dwMajorVersion,
Version.dwMinorVersion,
Version.dwBuildNumber,
*Version.szCSDVersion ? L" - " : L"",
Version.szCSDVersion,
suiteInfo,
dwMask);
} else {
ClRtlLogPrint( LOG_UNUSUAL,
" OS Version not available! (error %1!u!)\n",
GetLastError()
);
}
//
// log the local time so we can correlate other logs which show local time
//
GetLocalTime( &localTime );
ClRtlLogPrint( LOG_NOISE,
" Local Time is "\
" %1!02d!/%2!02d!/%3!02d!-%4!02d!:%5!02d!:%6!02d!.%7!03d!\n",
localTime.wYear,
localTime.wMonth,
localTime.wDay,
localTime.wHour,
localTime.wMinute,
localTime.wSecond,
localTime.wMilliseconds);
Status = ClRtlBuildClusterServiceSecurityDescriptor( NULL );
if (Status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL, "[CS] Failed to build cluster security descriptor %1!x!\n",
Status);
goto init_failed;
}
//get params set in the registry
Status = CspGetServiceParams();
if (Status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL, "[CS] Failed to read service params %1!d!\n",
Status);
goto init_failed;
}
//the params on the command line over ride the ones in the registry
if (CsRunningAsService == FALSE) {
for (i=1; i<argc; i++) {
if (lstrcmpiW( L"loglevel", argv[i]+1) == 0) {
if (argc < i+2) {
CspUsage();
}
CsLogLevel = _wtoi(argv[++i]);
}
#ifdef CLUSTER_TESTPOINT
else if (lstrcmpiW( L"testpt", argv[i]+1 ) == 0 ) {
if (argc < i+2) {
CspUsage();
}
CsTestPoint = _wtoi(argv[++i]);
}
else if ( lstrcmpiW( L"persistent", argv[i]+1 ) == 0 ) {
CsPersistentTestPoint = TRUE;
}
else if ( lstrcmpiW( L"trigger", argv[i]+1 ) == 0 ) {
if ( argc < i+2 ) {
CspUsage();
}
CsTestTrigger = _wtoi(argv[++i]);
}
else if ( lstrcmpiW( L"action", argv[i]+1 ) == 0 ) {
if ( argc < i+2 ) {
CspUsage();
}
CsTestAction = _wtoi(argv[++i]);
}
#endif // CLUSTER_TESTPOINT
#if CLUSTER_BETA
else if ( lstrcmpiW( L"norpcauth", argv[i]+1 ) == 0 ) {
CsUseAuthenticatedRPC = FALSE;
}
#endif // CLUSTER_BETA
else if ( lstrcmpiW( L"debugresmon", argv[i]+1 ) == 0 ) {
CsDebugResmon = TRUE;
//
// check for optional, non-NULL command string
//
if ( argc >= i+2 ) {
if ( *argv[i+1] != L'-' && *argv[i+1] != L'/' && *argv[i+1] != UNICODE_NULL ) {
CsResmonDebugCmd = argv[++i];
}
}
}
else if ( lstrcmpiW( L"novercheck", argv[i]+1 ) == 0 ) {
CsNoVersionCheck = TRUE;
}
else if ( lstrcmpiW( L"noquorumlogging", argv[i]+1 ) == 0 ) {
CsNoQuorumLogging = TRUE;
CsUserTurnedOffQuorumLogging = TRUE;
}
else if ( lstrcmpiW( L"fixquorum", argv[i]+1 ) == 0 ) {
CsNoQuorum = TRUE;
CsNoQuorumLogging = TRUE;
CsUserTurnedOffQuorumLogging = TRUE;
}
else if ( lstrcmpiW( L"resetquorumlog", argv[i]+1 ) == 0 ) {
CsResetQuorumLog = TRUE;
}
else if ( lstrcmpiW( L"forcequorum", argv[i]+1 ) == 0 ) {
CsForceQuorum = TRUE;
CsCommandLineForceQuorum = TRUE;
if (( argc < i+2 )
|| ( *argv[i+1] == L'-' )
|| ( *argv[i+1] == L'/' )) {
CsForceQuorumNodes = NULL;
} else {
CsForceQuorumNodes = argv[++i];
}
}
else if ( lstrcmpiW( L"norepevtlogging", argv[i]+1 ) == 0 ) {
CsNoRepEvtLogging = TRUE;
}
else if ( lstrcmpiW( L"debug", argv[i]+1 ) == 0 ) {
debugFlagFound = TRUE;
}
else if ( lstrcmpiW( L"restoredatabase", argv[i]+1 ) == 0 ) {
if ( ( argc < i+2 ) ||
( *argv[i+1] == L'-' ) ||
( *argv[i+1] == L'/' ) )
{
printf("\n\n*** restoredatabase option needs a path parameter ***\n\n");
CspUsage();
}
if ( !ClRtlIsPathValid( argv[i+1] )) {
printf( "\n\n*** restoredatabase path is invalid ***\n\n" );
CspUsage();
}
if ( !ClRtlPathFileExists( argv[i+1] )) {
printf( "\n\n*** restoredatabase file cannot be accessed ***\n\n" );
CspUsage();
}
dwLen = lstrlenW ( argv[++i] );
CsDatabaseRestorePath = (LPWSTR) LocalAlloc (LMEM_FIXED,
( dwLen + 1 ) * sizeof ( WCHAR ) );
if ( CsDatabaseRestorePath == NULL ) {
printf("Error %d in allocating storage for restoredatabase path name (%ws)...\n",
GetLastError(),
argv[i]);
CspUsage();
}
wcscpy( CsDatabaseRestorePath, argv[i] );
CsDatabaseRestore = TRUE;
}
else if ( lstrcmpiW( L"quodriveletter", argv[i]+1 ) == 0 ) {
if ( ( argc < i+2 ) ||
( *argv[i+1] == L'-' ) ||
( *argv[i+1] == L'/' ) )
{
printf("\n\n*** quodriveletter option needs a drive letter parameter ***\n\n");
CspUsage();
}
dwLen = lstrlenW ( argv[++i] );
if ( ( dwLen != 2 ) ||
!iswalpha( *argv[i] ) ||
( *( argv[i]+1 ) != L':' ) ) {
printf("\n\n*** invalid drive letter %ws supplied with quodriveletter option ***\n\n",
argv[i]);
CspUsage();
}
CsQuorumDriveLetter = (LPWSTR) LocalAlloc (LMEM_FIXED,
( dwLen + 1 ) * sizeof ( WCHAR ) );
if ( CsQuorumDriveLetter == NULL ) {
printf("Error %d in allocating storage for quodriveletter option (%ws)...\n\n",
GetLastError(),
argv[i]);
CspUsage();
}
wcscpy( CsQuorumDriveLetter, argv[i] );
}
else if ( lstrcmpiW( L"forcerestore", argv[i]+1 ) == 0 ) {
CsForceDatabaseRestore = TRUE;
}
else {
CspUsage();
}
}
if (!debugFlagFound && !CspStopEvent) {
CspUsage();
}
if ( CsDatabaseRestore == TRUE ) {
if ( CsNoQuorumLogging || CsNoQuorum ) {
printf("\n\n**** restoredatabase cannot be used with noquorumlogging/fixquorum options ****\n\n");
CspUsage();
}
} else if ( CsForceDatabaseRestore ) {
printf("\n\n**** forcerestore cannot be used without restoredatabase option ****\n\n");
CspUsage();
}
if ( ( CsQuorumDriveLetter != NULL ) && !CsForceDatabaseRestore ) {
printf("\n\n**** quodriveletter cannot be used without forcerestore option ****\n\n");
CspUsage();
}
}
//
// Create our stop event
//
Status = ERROR_SUCCESS;
if (!CspStopEvent)
{
CspStopEvent = CreateEvent(
NULL, // default security
FALSE, // auto-reset
FALSE, // initial state is non-signalled
NULL // unnamed event
);
if (CspStopEvent == NULL) {
Status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[CS] Unable to create stop event, %1!u!\n",
Status);
}
}
//
// we can't fire up our main routine if we're running as a service until
// now (race conditions between reading startup params out of the registry
// versus whether we're running as a service at all, etc.). Note that we
// failed initialization so if we are running as a service, we'll detect
// it in CspServiceMain and issue the stop
//
init_failed:
CspInitStatus = Status;
//
// Run the service.
//
if (CsRunningAsService) {
if (!StartServiceCtrlDispatcher(dispatchTable)) {
Status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[CS] Unable to dispatch to SC, %1!u!\n",
Status);
CL_UNEXPECTED_ERROR( Status );
}
}
else if ( CspInitStatus == ERROR_SUCCESS ) {
Status = CspDebugService(argc, argv);
}
ClRtlFreeClusterServiceSecurityDescriptor( );
return(Status);
}
void CsGetClusterVersionInfo(
IN PCLUSTERVERSIONINFO pClusterVersionInfo)
{
OSVERSIONINFOW OsVersionInfo;
pClusterVersionInfo->dwVersionInfoSize = sizeof(CLUSTERVERSIONINFO);
pClusterVersionInfo->MajorVersion = (WORD)VER_PRODUCTVERSION_W >> 8;
pClusterVersionInfo->MinorVersion = (WORD)VER_PRODUCTVERSION_W & 0xff;
pClusterVersionInfo->BuildNumber = (WORD)CLUSTER_GET_MINOR_VERSION(CsMyHighestVersion);
mbstowcs(pClusterVersionInfo->szVendorId, VER_CLUSTER_PRODUCTNAME_STR,
(lstrlenA(VER_CLUSTER_PRODUCTNAME_STR)+1));
OsVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionExW(&OsVersionInfo);
lstrcpynW(pClusterVersionInfo->szCSDVersion, OsVersionInfo.szCSDVersion,
(sizeof(pClusterVersionInfo->szCSDVersion)/sizeof(WCHAR)));
pClusterVersionInfo->dwReserved = 0;
NmGetClusterOperationalVersion(&(pClusterVersionInfo->dwClusterHighestVersion),
&(pClusterVersionInfo->dwClusterLowestVersion),&(pClusterVersionInfo->dwFlags));
}