/*++ Copyright (c) 1991 Microsoft Corporation Module Name: control.c Abstract: Contains code for setting up and maintaining the control interface and sending controls to services. Functions in this module: RControlService ScCreateControlInstance ScWaitForConnect ScSendControl ScInitTransactNamedPipe ScEndTransactNamedPipe ScShutdownAllServices Author: Dan Lafferty (danl) 20-Mar-1991 Environment: User Mode -Win32 Revision History: 28-May-1996 AnirudhS ScSendControl, ScWaitForConnect and ScCleanoutPipe: If we time out waiting for a named pipe operation to complete, cancel it before returning. Otherwise it trashes the stack if it does complete later. 21-Feb-1995 AnirudhS ScShutdownAllServices: Fixed logic to wait for services in pending stop state. 19-Oct-1993 Danl Initialize the Overlapped structures that are allocated on the stack. 20-Jul-1993 danl SendControl: If we get ERROR_PIPE_BUSY back from the transact call, then we need to clean out the pipe by reading it first - then do the transact. 29-Dec-1992 danl Simplified calculation of elapsed time. This removed complier warning about overflow in constant arithmetic. 06-Mar-1992 danl SendControl: Fixed heap trashing problem where it didn't allocate the 4 extra alignment bytes in the case where there are no arguments. The registry name path becomes an argument even if there are no other agruments. Therefore it requires alignment for any start cmd. 20-Feb-1992 danl Get Pipe Handle only after we know we have an active service & the image record is good. 20-Feb-1992 danl Only add 4 extra alignment bytes to control buffer when there are arguments to pass. 31-Oct-1991 danl Fixed the logic governing the behavior under various service state and control opcode conditions. Added State Table to description. This logic was taken directly from LM2.0. 03-Sept-1991 danl Fixed alignment problem when marshalling args in ScSendControl. The array of offsets needs to be 4 byte aligned after the Service Name. 20-Mar-1991 danl created --*/ // // INCLUDES // #include #include // DbgPrint prototype #include // Can't use this until MIDL allows VOIDs #include // needed for winbase.h when ntrtl is present #include // Pipe definitions #include // DataTypes and runtime APIs #include // wide character c runtimes. #include // MIDL generated header file. (SC_RPC_HANDLE) //#include // strlen #include // Unicode string macros #include // ROUND_UP_POINTER macro #include // SC_LOG #include "dataman.h" // LPSERVICE_RECORD #include #include // ScCreateAndSetSD #include "scopen.h" // Handle structures and signature definitions #include "depend.h" // ScDependentsStopped() #include "driver.h" // ScControlDriver() #include "svcctrl.h" // ScShutdownInProgress // // STATIC DATA // static CRITICAL_SECTION ScTransactNPCriticalSection; // // Constants // #define MAX_INSTANCES 100 // Maximum number of pipe instances. #define SC_PIPE_TRANSACT_TIMEOUT 120000 // 2 minutes #define SC_PIPE_CLEANOUT_TIMEOUT 30 // 30 msec // // Range for OEM defined control opcodes // #define OEM_LOWER_LIMIT 128 #define OEM_UPPER_LIMIT 255 // // Local Function Prototypes // VOID ScCleanOutPipe( HANDLE PipeHandle ); /****************************************************************************/ DWORD RControlService ( IN SC_RPC_HANDLE hService, IN DWORD OpCode, OUT LPSERVICE_STATUS lpServiceStatus ) /*++ Routine Description: RPC entry point for the RServiceControl API function. The following state table describes what is to happen under various state/Opcode conditions: [OpCode] STOP INTERROGATE OTHER [Current State] ______________________________________ | | | | STOPPED | (c) | (c) | (c) | | | | | STOP_PENDING | (c) | (c) | (c) | | | | | START_PEND | (a) | (b) | (c) | | | | | RUNNING | (a) | (a) | (a) | | | | | CONTINUE_PEND | (a) | (a) | (a) | | | | | PAUSE_PENDING | (a) | (a) | (a) | | | | | PAUSED | (a) | (a) | (a) | |___________|____________|____________| (a) Send control code to the service if the service is set up to receive this type of opcode. If it is not set up to receive the opcode, return NERR_ServiceCtrlNotValid. An example of this would be the case of sending a PAUSE to a service that is listed as NOT_PAUSABLE. (b) Do NOT send control code to the service. Instead, return the last known state of the service with a SUCCESS status. (c) Do NOT send control code to the service. Instead return ERROR_SERVICE_CANNOT_ACCEPT_CTRL. Arguments: hService - This is a handle to the service. It is actually a pointer to a service handle structure. OpCode - The control request code. lpServiceStatus - pointer to a location where the service status is to be returned. If this pointer is invalid, it will be set to NULL upon return. Return Value: The returned lpServiceStatus structure is valid as long as the returned status is NO_ERROR. NO_ERROR - The operation was successful. ERROR_INVALID_HANDLE - The handle passed in was not a valid hService handle. NERR_InternalError - LocalAlloc or TransactNamedPipe failed, or TransactNamedPipe returned fewer bytes than expected. ERROR_SERVICE_REQUEST_TIMEOUT - The service did not respond with a status message within the fixed timeout limit (RESPONSE_WAIT_TIMEOUT). NERR_ServiceKillProc - The service process had to be killed because it wouldn't terminate when requested. ERROR_SERVICE_CANNOT_ACCEPT_CTRL - The service cannot accept control messages at this time. ERROR_INVALID_SERVICE_CONTROL - The request is not valid for this service. For instance, a PAUSE request is not valid for a service that lists itself as NOT_PAUSABLE. ERROR_INVALID_PARAMETER - The requested control is not valid. ERROR_ACCESS_DENIED - This is a status response from the service security check. Note: Because there are multiple services in a process, we cannot simply kill the process if the service does not respond to a terminate request. This situation is handled by first checking to see if this is the last service in the process. If it is, then it is removed from the installed database, and the process is terminated. If it isn't the last service, then we indicate timeout and do nothing. --*/ { DWORD status = NO_ERROR; LPSERVICE_RECORD serviceRecord; DWORD currentState; DWORD controlsAccepted; HANDLE pipeHandle; LPWSTR serviceName; ACCESS_MASK desiredAccess; if (ScShutdownInProgress) { return(ERROR_SHUTDOWN_IN_PROGRESS); } // // Check the signature on the handle. // if (((LPSC_HANDLE_STRUCT)hService)->Signature != SERVICE_SIGNATURE) { return(ERROR_INVALID_HANDLE); } #ifdef SC_DEBUG //**************************************************************************** if (OpCode == 5555) { ScShutdownNotificationRoutine(CTRL_SHUTDOWN_EVENT); } //**************************************************************************** #endif // // Set the desired access based on the control requested. // switch (OpCode) { case SERVICE_CONTROL_STOP: desiredAccess = SERVICE_STOP; break; case SERVICE_CONTROL_PAUSE: case SERVICE_CONTROL_CONTINUE: desiredAccess = SERVICE_PAUSE_CONTINUE; break; case SERVICE_CONTROL_INTERROGATE: desiredAccess = SERVICE_INTERROGATE; break; default: if ((OpCode >= OEM_LOWER_LIMIT) && (OpCode <= OEM_UPPER_LIMIT)) { desiredAccess = SERVICE_USER_DEFINED_CONTROL; } else { return(ERROR_INVALID_PARAMETER); } } // // Was the handle opened with desired control access? // if (! RtlAreAllAccessesGranted( ((LPSC_HANDLE_STRUCT)hService)->AccessGranted, desiredAccess )) { return(ERROR_ACCESS_DENIED); } serviceRecord = ((LPSC_HANDLE_STRUCT)hService)->Type.ScServiceObject.ServiceRecord; // // If this control is for a driver, call ScControlDriver and return. // if (serviceRecord->ServiceStatus.dwServiceType & SERVICE_DRIVER) { return(ScControlDriver(OpCode, serviceRecord, lpServiceStatus)); } // // Obtain a shared lock on the database - read the data we need, // Then free the lock. // ScDatabaseLock(SC_GET_SHARED, "Control1"); currentState = serviceRecord->ServiceStatus.dwCurrentState; controlsAccepted = serviceRecord->ServiceStatus.dwControlsAccepted; serviceName = serviceRecord->ServiceName; // // If we can obtain a pipe handle, do so. Otherwise, return an error. // (but first release the lock). // if ((currentState != SERVICE_STOPPED) && (serviceRecord->ImageRecord != NULL)) { pipeHandle = serviceRecord->ImageRecord->PipeHandle; } else { status = ERROR_SERVICE_NOT_ACTIVE; } ScDatabaseLock(SC_RELEASE, "Control2"); if (status != NO_ERROR) { return(status); } // // The control is not sent to the service if the service is not // RUNNING (meaning running, or in one of the pause/continue states. // EXCEPT - we allow STOP controls to a service that is START_PENDING. // // If we decide not to allow the control to be sent, we either // return current info (INTERROGATE) or an error (any other opcode). // if ((currentState == SERVICE_STOPPED) || (currentState == SERVICE_STOP_PENDING)) { return(ERROR_SERVICE_CANNOT_ACCEPT_CTRL); } else if (currentState == SERVICE_START_PENDING) { switch(OpCode) { case SERVICE_CONTROL_INTERROGATE: // // In this case we will just return the last known status. // memcpy( (PVOID)lpServiceStatus, (PVOID)&(serviceRecord->ServiceStatus), sizeof(SERVICE_STATUS)); return(NO_ERROR); case SERVICE_CONTROL_STOP: break; default: return(ERROR_SERVICE_CANNOT_ACCEPT_CTRL); } } // // Check to see if the control request is valid for the service. // if ( ( (OpCode == SERVICE_CONTROL_PAUSE || OpCode == SERVICE_CONTROL_CONTINUE) && ((controlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE) == 0) ) || ( (OpCode == SERVICE_CONTROL_STOP) && ((controlsAccepted & SERVICE_ACCEPT_STOP) == 0) ) ) { return(ERROR_INVALID_SERVICE_CONTROL); } // // Check for dependent services still running // if (OpCode == SERVICE_CONTROL_STOP) { ScDatabaseLock(SC_GET_SHARED, "Control3"); if (! ScDependentsStopped(serviceRecord)) { ScDatabaseLock(SC_RELEASE, "Control4"); return(ERROR_DEPENDENT_SERVICES_RUNNING); } ScDatabaseLock(SC_RELEASE, "Control4"); } // // Send the control request to the target service // status = NO_ERROR; status = ScSendControl ( serviceName, // ServiceName pipeHandle, // pipeHandle OpCode, // Opcode NULL, // CmdArgs (vector ptr) 0L, // NumArgs 0L); // StatusHandle if (status == NO_ERROR) { // // If no errors occured, copy the latest status into the return // buffer. The shared lock is required for this. // ScDatabaseLock(SC_GET_SHARED, "Control5"); memcpy( (PVOID)lpServiceStatus, (PVOID)&(serviceRecord->ServiceStatus), sizeof(SERVICE_STATUS)); ScDatabaseLock(SC_RELEASE, "Control6"); } else { SC_LOG2(ERROR,"RControlService:SendControl to %ws service failed %ld\n", serviceRecord->ServiceName, status); if (OpCode == SERVICE_CONTROL_STOP) { // // If sending the control failed, and the control was a request // to stop, and if this service is the only running service in // the process, we can force the process to stop. ScRemoveService // will handle this if the ServiceCount is one. // ScDatabaseLock(SC_GET_SHARED, "Control7"); if (serviceRecord->ImageRecord != NULL) { if (serviceRecord->ImageRecord->ServiceCount == 1) { SC_LOG0(TRACE,"RControlService:Forcing Service Shutdown\n"); ScRemoveService(serviceRecord); } } ScDatabaseLock(SC_RELEASE, "Control8"); } } return(status); } /****************************************************************************/ DWORD ScCreateControlInstance ( OUT LPHANDLE PipeHandlePtr ) /*++ Routine Description: This function creates an instance of the control pipe Arguments: PipeHandlePtr - This is a pointer to a location where the pipe handle is to be placed upon return. Return Value: NO_ERROR - The operation was successful. other - Any error returned by CreateNamedPipe could be returned. --*/ { DWORD status; NTSTATUS ntstatus; SECURITY_ATTRIBUTES SecurityAttr; PSECURITY_DESCRIPTOR SecurityDescriptor; SC_ACE_DATA AceData[1] = { {ACCESS_ALLOWED_ACE_TYPE, 0, 0, GENERIC_ALL, &WorldSid} }; // // Create a security descriptor for the control named pipe so // that we can grant world access to it. // ntstatus = ScCreateAndSetSD( AceData, 1, LocalSystemSid, LocalSystemSid, &SecurityDescriptor ); if (! NT_SUCCESS(ntstatus)) { SC_LOG1(ERROR, "ScCreateAndSetSD failed " FORMAT_NTSTATUS "\n", ntstatus); return(RtlNtStatusToDosError(ntstatus)); } SecurityAttr.nLength = sizeof(SECURITY_ATTRIBUTES); SecurityAttr.lpSecurityDescriptor = SecurityDescriptor; SecurityAttr.bInheritHandle = FALSE; // // Create the service controller's end of the named pipe that will // be used for communicating control requests to the service process. // // NOTE: We could use a security descriptor on the creation to assure // that the connecting process has the appropriate clearance. // *PipeHandlePtr = CreateNamedPipe ( CONTROL_PIPE_NAME, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_WAIT | PIPE_READMODE_MESSAGE | PIPE_TYPE_MESSAGE, MAX_INSTANCES, // max num of instances. 8000, 4, CONTROL_TIMEOUT, // Default Timeout &SecurityAttr); // Security Descriptor status = NO_ERROR; if (*PipeHandlePtr == (HANDLE)0xFFFFFFFF) { status = GetLastError(); SC_LOG1(ERROR, "CreateControlInstance: CreateNamedPipe failed, %ld\n",status); } (void) RtlDeleteSecurityObject(&SecurityDescriptor); return(status); } /****************************************************************************/ DWORD ScWaitForConnect ( IN HANDLE PipeHandle, OUT LPDWORD ProcessIdPtr ) /*++ Routine Description: This function waits until a connection is made to the pipe handle. It then waits for the first status message to be sent from the service process. The first message from the service contains the processId. This helps to verify that we are talking to the correct process. Arguments: PipeHandle - This is the handle to the pipe instance that is waiting for a connect. ProcessIdPtr - This is a pointer to the location where the processId is to be stored. Return Value: NO_ERROR - The pipe is in the connected state. any error that ReadFile can produce may be returned. Note: The ConnectNamedPipe called is done asynchronously and we wait on its completion using the pipe handle. This can only work correctly when it is guaranteed that no other IO is issued while we are waiting on the pipe handle (except for the service itself to connect to the pipe with call to CreateFile). --*/ { PIPE_RESPONSE_MSG serviceResponseBuffer; DWORD numBytesRead; BOOL status; DWORD apiStatus; OVERLAPPED overlapped={0,0,0,0,0};// overlapped structure to implement // timeout on TransactNamedPipe LPWSTR ScSubStrings[1]; WCHAR ScErrorCodeString[25]; SC_LOG(TRACE,"ServiceController waiting for pipe connect\n",0); overlapped.hEvent = (HANDLE) NULL; // Wait on pipe handle // // Wait for the service to connect. // status = ConnectNamedPipe(PipeHandle, &overlapped); if (status == FALSE) { apiStatus = GetLastError(); if (apiStatus == ERROR_IO_PENDING) { // // Connection is pending // apiStatus = WaitForSingleObject(PipeHandle, SC_PIPE_TRANSACT_TIMEOUT); if (apiStatus == WAIT_TIMEOUT) { SC_LOG(ERROR, "ScWaitForConnect: Wait for connection for %u secs timed out\n", SC_PIPE_TRANSACT_TIMEOUT / 1000 ); // // The service didn't respond. Cancel the named pipe operation. // status = CancelIo(PipeHandle); if (status == FALSE) { SC_LOG(ERROR, "ScWaitForConnect: CancelIo failed, %lu\n", GetLastError()); } ScSubStrings[0] = ultow(SC_PIPE_TRANSACT_TIMEOUT, ScErrorCodeString, 10); ScLogEvent( EVENT_CONNECTION_TIMEOUT, 1, ScSubStrings ); return ERROR_SERVICE_REQUEST_TIMEOUT; } else if (apiStatus == 0) { // // Wait completed successfully // status = GetOverlappedResult( PipeHandle, &overlapped, &numBytesRead, TRUE ); if (status == FALSE) { apiStatus = GetLastError(); SC_LOG(ERROR, "ScWaitForConnect: GetOverlappedResult failed, rc=%lu\n", apiStatus); return apiStatus; } } } else if (apiStatus != ERROR_PIPE_CONNECTED) { SC_LOG(ERROR,"ScWaitForConnect: ConnectNamedPipe failed, rc=%lu\n", apiStatus); return apiStatus; } // // If we received the ERROR_PIPE_CONNECTED status, then things // are still ok. // } SC_LOG(TRACE,"WaitForConnect:ConnectNamedPipe Success\n",0); // // Wait for initial status message // overlapped.hEvent = (HANDLE) NULL; // Wait on pipe handle status = ReadFile ( PipeHandle, (LPVOID)&serviceResponseBuffer, sizeof(serviceResponseBuffer), &numBytesRead, &overlapped); if (status == FALSE) { apiStatus = GetLastError(); if (apiStatus == ERROR_IO_PENDING) { // // Connection is pending // apiStatus = WaitForSingleObject(PipeHandle, SC_PIPE_TRANSACT_TIMEOUT); if (apiStatus == WAIT_TIMEOUT) { SC_LOG(ERROR, "ScWaitForConnect: Wait for ReadFile for %u secs timed out\n", SC_PIPE_TRANSACT_TIMEOUT / 1000 ); // // Cancel the named pipe operation. // status = CancelIo(PipeHandle); if (status == FALSE) { SC_LOG(ERROR, "ScWaitForConnect: CancelIo failed, %lu\n", GetLastError()); } ScSubStrings[0] = ultow(SC_PIPE_TRANSACT_TIMEOUT, ScErrorCodeString, 10); ScLogEvent( EVENT_READFILE_TIMEOUT, 1, ScSubStrings ); return ERROR_SERVICE_REQUEST_TIMEOUT; } else if (apiStatus == 0) { // // Wait completed successfully // status = GetOverlappedResult( PipeHandle, &overlapped, &numBytesRead, TRUE ); if (status == FALSE) { apiStatus = GetLastError(); SC_LOG(ERROR, "ScWaitForConnect: GetOverlappedResult for ReadFile failed, rc=%lu\n", apiStatus); return apiStatus; } } } else { SC_LOG(ERROR,"ScWaitForConnect: ReadFile failed, rc=%lu\n", apiStatus); return apiStatus; } } SC_LOG0(TRACE,"WaitForConnect:ReadFile success\n"); SC_LOG( TRACE, "WaitForConnect:ReadFile buffer size = %ld\n", sizeof(serviceResponseBuffer)); SC_LOG( TRACE, "WaitForConnect:ReadFile numBytesRead = %ld\n", numBytesRead); *ProcessIdPtr = serviceResponseBuffer.DispatcherStatus; return(NO_ERROR); } /****************************************************************************/ DWORD ScSendControl ( IN LPWSTR ServiceName, IN HANDLE PipeHandle, IN DWORD OpCode, IN LPWSTR *CmdArgs OPTIONAL, IN DWORD NumArgs, IN DWORD StatusHandle OPTIONAL ) /*++ Routine Description: This function sends a control request to a service via a TransactNamedPipe call. A buffer is allocated for the transaction, and then freed when done. LOCKS: Normally locks are not held when this function is called. This is because we need to allow status messages to come in prior to the transact completing. The exception is when we send the message to the control dispatcher to shutdown. No status message is sent in response to that. There is a ScTransactNPCriticalSection that is held during the actual Transact. Arguments: ServiceName - This is a pointer to a NUL terminated service name string. PipeHandle - This is the pipe handle to which the request is directed. OpCode - This is the opcode that is to be passed to the service. CmdArgs - This is an optional pointer an array of pointers to NUL terminated strings. These strings are the command line information that the service is to be started with. This parameter is only used when the OpCode is SERVICE_CONTROL_START. NumArgs - This indicates how many arguments are in the CmdArgs array. ServiceRegPath - This is an optional pointer to a NUL terminated string that contains the service's registry key path. StatusHandle - This is a handle that the Service is expected to use when calling SetServiceStatus. This is actually a pointer to a service record. This is only required when sending the control that will start a service. Return Value: NO_ERROR - The operation was successful. ERROR_GEN_FAILURE - An incorrect number of bytes was received in the response message. ERROR_ACCESS_DENIED - This is a status response from the service security check by the Control Dispatcher on the other end of the pipe. ERROR_NOT_ENOUGH_MEMORY - Unable to allocate memory for the transaction buffer. (Local Alloc failed). other - Any error from TransactNamedPipe could be returned - Or any error from the Control Dispatcher on the other end of the pipe. --*/ { DWORD returnStatus = NO_ERROR; LPBYTE buffer; // the send buffer. LPCTRL_MSG_HEADER pipeSendMsg; // buffer with structure DWORD serviceNameSize; DWORD sendBufferSize; BOOL status; PIPE_RESPONSE_MSG serviceResponseBuffer; DWORD bytesRead; DWORD stringOffset; // offset to the strings. LPWSTR *vectorPtr; // pointer current location for arg ptr. DWORD i; OVERLAPPED overlapped={0,0,0,0,0};// overlapped structure to implement // timeout on TransactNamedPipe LPWSTR ScSubStrings[1]; WCHAR ScErrorCodeString[25]; // // Change the opcode for a forced shut down so that we ask politely. // if(OpCode == SERVICE_CONTROL_FORCE_STOP) { OpCode = SERVICE_CONTROL_STOP; } serviceNameSize = WCSSIZE(ServiceName); sendBufferSize = serviceNameSize + sizeof(CTRL_MSG_HEADER); // // Add an extra 4 byte offset to help settle alignment problems that // may occur when the array of pointers follows the service name string. // sendBufferSize += 4; // // The CmdArgs array offset is an offset from the top of the message // send buffer. // if (CmdArgs != NULL) { for (i=0; iOpCode = OpCode; pipeSendMsg->Count = sendBufferSize; pipeSendMsg->StatusHandle = StatusHandle; // // Copy the service name to buffer and store the offset. // pipeSendMsg->ServiceNameOffset = sizeof(CTRL_MSG_HEADER); wcscpy((LPWSTR)(buffer + sizeof(CTRL_MSG_HEADER)), ServiceName); // // if there are arguments present, then this control must be // for starting a service. // if (NumArgs > 0) { // // Calculate the beginning of the string area and the beginning // of the arg vector area. Align the vector pointer on a 4 byte // boundary. // vectorPtr = (LPWSTR *)(buffer + sizeof(CTRL_MSG_HEADER) + serviceNameSize); vectorPtr = ROUND_UP_POINTER(vectorPtr,4); pipeSendMsg->ArgvOffset = (LPBYTE)vectorPtr - buffer; pipeSendMsg->NumCmdArgs = NumArgs; // // Determine the offset from the top of the argv array to the // first argv string. Also determine the pointer value for that // location. // stringOffset = (pipeSendMsg->NumCmdArgs) * sizeof(LPWSTR); // // Copy the command arg strings to the buffer and update the argv // pointers with offsets. Remember - we already have one argument // in there for the service registry path. // if (NumArgs != 0) { for (i=0; iServiceStatus.dwServiceType & SERVICE_WIN32) && (Service->ServiceStatus.dwCurrentState != SERVICE_STOPPED) && (Service->ServiceStatus.dwCurrentState != SERVICE_STOP_PENDING) && (Service->ServiceStatus.dwControlsAccepted & SERVICE_ACCEPT_SHUTDOWN) && (Service->ImageRecord != NULL)) { SC_LOG1(TRACE,"Shutdown: Sending Stop to Service : %ws\n", Service->ServiceName); status = ScSendControl ( Service->ServiceName, Service->ImageRecord->PipeHandle, SERVICE_CONTROL_SHUTDOWN, NULL, // CmdArgs 0L, // NumArgs 0L); // StatusHandle if (status != NO_ERROR) { SC_LOG1(ERROR,"ScShutdownAllServices: ScSendControl " "Failed for %ws\n",Service->ServiceName); } else { // // Save the services that have been sent stop requests // in the temporary array. // SC_ASSERT(serviceIndex < arraySize); if (serviceIndex < arraySize) { affectedServices[serviceIndex++] = Service; } } } Service = Service->Next; } SC_LOG0(TRACE,"***** DONE SENDING STOPS TO SERVICES *****\n"); //------------------------------------------------------------------- // // Now check to see if these services stopped. // //------------------------------------------------------------------- startTime = GetTickCount(); arrayEnd = serviceIndex; ServicesStopping = (serviceIndex != 0); SC_LOG(TRACE,"Waiting for services to stop. Start time is %lu\n", startTime); while (ServicesStopping) { // // Wait a bit for the services to become stopped. // Sleep(500); // // We are going to check all the services in our shutdown // list and see if we still have services to wait on. // ServicesStopping = FALSE; maxWait = 0; for (serviceIndex = 0; serviceIndex < arrayEnd ; serviceIndex++) { Service = affectedServices[serviceIndex]; // // If the service is in the stop pending state, then wait // a bit and check back. Maximum wait time is the maximum // wait hint period of all the services. If a service's // wait hint is 0, use 20 seconds as its wait hint. // // Note that this is different from how dwWaitHint is // interpreted for all other operations. We ignore // dwCheckPoint here. // switch (Service->ServiceStatus.dwCurrentState) { case SERVICE_STOP_PENDING: SC_LOG2(TRACE, "%ws Service is still pending, wait hint = %lu\n", Service->ServiceName, Service->ServiceStatus.dwWaitHint); if (Service->ServiceStatus.dwWaitHint == 0) { if (maxWait < 20000) { maxWait = 20000; } } else { if (maxWait < Service->ServiceStatus.dwWaitHint) { maxWait = Service->ServiceStatus.dwWaitHint; } } ServicesStopping = TRUE; break; case SERVICE_STOPPED: SC_LOG(TRACE, "%ws Service stopped successfully\n", Service->ServiceName); break; default: // // This is an error. But we can't do anything about // it, so it will be ignored. // SC_LOG2(TRACE,"ERROR: %ws Service is in invalid state %#lx\n", Service->ServiceName, Service->ServiceStatus.dwCurrentState); break; } // end switch } // end for // // We have examined all the services. If there are still services // with the STOP_PENDING, then see if we have timed out the // maxWait period yet. // if (ServicesStopping) { if ( (GetTickCount() - startTime) > maxWait ) { // // The maximum wait period has been exceeded. At this // point we should end this shutdown effort. There is // no point in forcing shutdown. So we just exit. // SC_LOG(TRACE,"The Services didn't stop within the timeout " "period of %lu.\n --- There is still at least one " "service running\n", maxWait); ServicesStopping = FALSE; } } } SC_LOG0(TRACE,"Done Waiting for services to stop\n"); } } VOID ScCleanOutPipe( HANDLE PipeHandle ) /*++ Routine Description: This function reads and throws away all data that is currently in the pipe. This function is called if the pipe is busy when it shouldn't be. The PIPE_BUSY occurs when (1) the transact never returns, or (2) the last transact timed-out, and the return message was eventually placed in the pipe after the timeout. This function is called to fix the (2) case by cleaning out the pipe. Arguments: PipeHandle - A Handle to the pipe to be cleaned out. Return Value: none. --*/ { #define EXPUNGE_BUF_SIZE 100 DWORD status; DWORD returnStatus; DWORD numBytesRead=0; BYTE msg[EXPUNGE_BUF_SIZE]; OVERLAPPED overlapped={0,0,0,0,0}; do { overlapped.hEvent = (HANDLE) NULL; // Wait on pipe handle status = ReadFile ( PipeHandle, msg, EXPUNGE_BUF_SIZE, &numBytesRead, &overlapped); if (status == FALSE) { returnStatus = GetLastError(); if (returnStatus == ERROR_IO_PENDING) { status = WaitForSingleObject( PipeHandle, SC_PIPE_CLEANOUT_TIMEOUT); if (status == WAIT_TIMEOUT) { SC_LOG0(ERROR, "ControlPipe was busy but we were unable to " "clean it out in the timeout period\n"); // // Cancel the named pipe operation. // status = CancelIo(PipeHandle); if (status == FALSE) { SC_LOG(ERROR, "ScCleanOutPipe: CancelIo failed, %lu\n", GetLastError()); } } } else { SC_LOG1(ERROR, "ControlPipe was busy. The attempt to clean" "it out failed with %d\n", returnStatus); } } } while (status == ERROR_MORE_DATA); }