2215 lines
66 KiB
C++
2215 lines
66 KiB
C++
/*++
|
||
|
||
Copyright (c) 1991 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
control.cxx
|
||
|
||
Abstract:
|
||
|
||
Contains code for setting up and maintaining the control interface
|
||
and sending controls to services. Functions in this module:
|
||
|
||
RControlService
|
||
RI_ScSendTSMessage
|
||
ScCreateControlInstance
|
||
ScWaitForConnect
|
||
ScSendControl
|
||
ScInitTransactNamedPipe
|
||
ScShutdownAllServices
|
||
|
||
Author:
|
||
|
||
Dan Lafferty (danl) 20-Mar-1991
|
||
|
||
Environment:
|
||
|
||
User Mode -Win32
|
||
|
||
Revision History:
|
||
|
||
13-Mar-1999 jschwart
|
||
Added per-account security on the SCM <--> service control pipe
|
||
|
||
07-Apr-1998 jschwart
|
||
ScInitTransactNamedPipe: Check registry for a user-supplied named
|
||
pipe timeout value before setting it to the default. With the old
|
||
default (30000 ms), services could be double-started on a heavily
|
||
loaded machine when the server would send to the client, the client's
|
||
response would time out, and the server would assume it hadn't started.
|
||
|
||
10-Mar-1998 jschwart
|
||
Added ScSendPnPMessage and code to ScSendControl to enable passing
|
||
of PnP-related service controls to services. Got rid of flag added
|
||
on 06-Aug-1997 since we can just check the passed-in OpCode instead
|
||
|
||
06-Aug-1997 jschwart
|
||
SendControl: Added flag to tell SendControl if it is sending a
|
||
shutdown message. If so, it uses WriteFile, since using
|
||
TransactNamedPipe with a poorly behaved service (that doesn't send
|
||
an ACK back to the SCM) otherwise hangs the SCM.
|
||
|
||
ShutdownAllServices: Since SendControl now uses WriteFile (asynch
|
||
write), added a case to the switch that checks to see if any services
|
||
are still running. If so, it gives them 30 seconds to become
|
||
STOP_PENDING before it gives up and shuts itself down.
|
||
|
||
05-Mar-1997 AnirudhS
|
||
Eliminated limit of 100 pipe instances.
|
||
|
||
04-Mar-1997 AnirudhS
|
||
Added PARAMCHANGE, NETBINDADD, etc. controls for Plug and Play.
|
||
|
||
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 "precomp.hxx"
|
||
#include <stdlib.h> // wide character c runtimes.
|
||
#include <tstr.h> // Unicode string macros
|
||
#include <align.h> // ROUND_UP_POINTER macro
|
||
#include <control.h>
|
||
#include <scseclib.h> // ScCreateAndSetSD
|
||
#include "depend.h" // ScDependentsStopped()
|
||
#include "driver.h" // ScControlDriver()
|
||
#include "sclib.h" // ScIsValidServiceName()
|
||
#include "scsec.h" // ScStatusAccessCheck()
|
||
|
||
#include <dbt.h> // PDEV_BROADCAST_HDR
|
||
|
||
|
||
//
|
||
// Constants
|
||
//
|
||
#define SC_DEFAULT_PIPE_TRANSACT_TIMEOUT 30000 // 30 sec
|
||
#define SC_PIPE_CLEANOUT_TIMEOUT 30 // 30 msec
|
||
|
||
//
|
||
// Registry key and value for the pipe timeout value
|
||
//
|
||
#define REGKEY_PIPE_TIMEOUT L"System\\CurrentControlSet\\Control"
|
||
#define REGVAL_PIPE_TIMEOUT L"ServicesPipeTimeout"
|
||
|
||
//
|
||
// Registry key, value, and constant for the shutdown performance metric
|
||
//
|
||
// #define SC_SHUTDOWN_METRIC
|
||
|
||
#ifdef SC_SHUTDOWN_METRIC
|
||
|
||
#define REGKEY_SHUTDOWN_TIMEOUT L"System\\CurrentControlSet\\Control"
|
||
#define REGVAL_SHUTDOWN_TIMEOUT L"ShutdownTimeout"
|
||
|
||
#endif // SC_SHUTDOWN_METRIC
|
||
|
||
//
|
||
// STATIC DATA
|
||
//
|
||
|
||
/* static */ CRITICAL_SECTION ScTransactNPCriticalSection;
|
||
|
||
//
|
||
// Globals
|
||
//
|
||
DWORD g_dwScPipeTransactTimeout = SC_DEFAULT_PIPE_TRANSACT_TIMEOUT;
|
||
|
||
|
||
//
|
||
// Local Structure/Function Prototypes
|
||
//
|
||
typedef struct
|
||
{
|
||
LPWSTR lpServiceName;
|
||
LPWSTR lpDisplayName;
|
||
HANDLE hPipe;
|
||
}
|
||
TS_CONTROL_INFO, *PTS_CONTROL_INFO, *LPTS_CONTROL_INFO;
|
||
|
||
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 | (b) | (b) | (b) |
|
||
| | | |
|
||
START_PEND | (a) | (d) | (b) |
|
||
| | | |
|
||
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 ERROR_INVALID_SERVICE_CONTROL.
|
||
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
|
||
ERROR_SERVICE_CANNOT_ACCEPT_CTRL.
|
||
|
||
(c) Do NOT send control code to the service. Instead, return
|
||
ERROR_SERVICE_NOT_ACTIVE.
|
||
|
||
(d) Do NOT send control code to the service. Instead, return
|
||
the last known state of the service with a SUCCESS status.
|
||
NOTE -- this case (and this case only) differs from the
|
||
SDK doc, which hides the fact that you can interrogate a
|
||
service that's in the START_PENDING state
|
||
|
||
|
||
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;
|
||
DWORD controlsAcceptedMask = 0;
|
||
HANDLE pipeHandle = NULL;
|
||
LPWSTR serviceName;
|
||
LPWSTR displayName;
|
||
ACCESS_MASK desiredAccess;
|
||
|
||
|
||
if (ScShutdownInProgress)
|
||
{
|
||
return(ERROR_SHUTDOWN_IN_PROGRESS);
|
||
}
|
||
|
||
//
|
||
// Check the handle.
|
||
//
|
||
if (!ScIsValidServiceHandle(hService))
|
||
{
|
||
return ERROR_INVALID_HANDLE;
|
||
}
|
||
|
||
#ifdef SC_DEBUG
|
||
|
||
//****************************************************************************
|
||
if (OpCode == 5555)
|
||
{
|
||
ScShutdownNotificationRoutine(CTRL_SHUTDOWN_EVENT);
|
||
}
|
||
//****************************************************************************
|
||
|
||
#endif // SC_DEBUG
|
||
|
||
//
|
||
// Set the desired access based on the control requested.
|
||
// Figure out which "controls accepted" bits must be set for the
|
||
// service to accept the control.
|
||
//
|
||
switch (OpCode) {
|
||
case SERVICE_CONTROL_STOP:
|
||
desiredAccess = SERVICE_STOP;
|
||
controlsAcceptedMask = SERVICE_ACCEPT_STOP;
|
||
break;
|
||
|
||
case SERVICE_CONTROL_PAUSE:
|
||
case SERVICE_CONTROL_CONTINUE:
|
||
desiredAccess = SERVICE_PAUSE_CONTINUE;
|
||
controlsAcceptedMask = SERVICE_ACCEPT_PAUSE_CONTINUE;
|
||
break;
|
||
|
||
case SERVICE_CONTROL_INTERROGATE:
|
||
desiredAccess = SERVICE_INTERROGATE;
|
||
break;
|
||
|
||
case SERVICE_CONTROL_PARAMCHANGE:
|
||
desiredAccess = SERVICE_PAUSE_CONTINUE;
|
||
controlsAcceptedMask = SERVICE_ACCEPT_PARAMCHANGE;
|
||
break;
|
||
|
||
case SERVICE_CONTROL_NETBINDADD:
|
||
case SERVICE_CONTROL_NETBINDREMOVE:
|
||
case SERVICE_CONTROL_NETBINDENABLE:
|
||
case SERVICE_CONTROL_NETBINDDISABLE:
|
||
desiredAccess = SERVICE_PAUSE_CONTINUE;
|
||
controlsAcceptedMask = SERVICE_ACCEPT_NETBINDCHANGE;
|
||
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.
|
||
//
|
||
{
|
||
CServiceRecordSharedLock RLock;
|
||
|
||
//
|
||
// Once we get to this point, copy in the last known
|
||
// status to return to the caller (Bug #188874)
|
||
//
|
||
|
||
RtlCopyMemory(lpServiceStatus,
|
||
&(serviceRecord->ServiceStatus),
|
||
sizeof(SERVICE_STATUS));
|
||
|
||
currentState = serviceRecord->ServiceStatus.dwCurrentState;
|
||
controlsAccepted = serviceRecord->ServiceStatus.dwControlsAccepted;
|
||
serviceName = serviceRecord->ServiceName;
|
||
displayName = serviceRecord->DisplayName;
|
||
|
||
//
|
||
// 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;
|
||
}
|
||
}
|
||
|
||
if (status != NO_ERROR) {
|
||
return(status);
|
||
}
|
||
|
||
//
|
||
// The control is not sent to the service if the service is in
|
||
// either the STOP_PENDING or START_PENDING state 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_STOP_PENDING) {
|
||
|
||
return(ERROR_SERVICE_CANNOT_ACCEPT_CTRL);
|
||
}
|
||
else if (currentState == SERVICE_START_PENDING) {
|
||
|
||
switch(OpCode) {
|
||
case SERVICE_CONTROL_INTERROGATE:
|
||
//
|
||
// Just return the last known status. This behavior is unpublished.
|
||
//
|
||
return(NO_ERROR);
|
||
|
||
case SERVICE_CONTROL_STOP:
|
||
break;
|
||
|
||
default:
|
||
return(ERROR_SERVICE_CANNOT_ACCEPT_CTRL);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Check if the service accepts the control.
|
||
//
|
||
if ( (controlsAccepted & controlsAcceptedMask) != controlsAcceptedMask ) {
|
||
|
||
return(ERROR_INVALID_SERVICE_CONTROL);
|
||
}
|
||
|
||
//
|
||
// Check for dependent services still running
|
||
//
|
||
BOOL fLastService = FALSE;
|
||
if (OpCode == SERVICE_CONTROL_STOP) {
|
||
|
||
CServiceRecordSharedLock RLock;
|
||
|
||
if (! ScDependentsStopped(serviceRecord)) {
|
||
return(ERROR_DEPENDENT_SERVICES_RUNNING);
|
||
}
|
||
|
||
if (serviceRecord->ImageRecord != NULL &&
|
||
serviceRecord->ImageRecord->ServiceCount == 1) {
|
||
|
||
fLastService = TRUE;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Send the control request to the target service
|
||
//
|
||
status = ScSendControl(serviceName, // ServiceName
|
||
displayName, // DisplayName
|
||
pipeHandle, // pipeHandle
|
||
OpCode, // Opcode
|
||
NULL, // CmdArgs (vector ptr)
|
||
0L, // NumArgs
|
||
NULL); // Ignore handler return value
|
||
|
||
if (status == NO_ERROR) {
|
||
//
|
||
// If no errors occured, copy the latest status into the return
|
||
// buffer. The shared lock is required for this.
|
||
//
|
||
CServiceRecordSharedLock RLock;
|
||
|
||
RtlCopyMemory(lpServiceStatus,
|
||
&(serviceRecord->ServiceStatus),
|
||
sizeof(SERVICE_STATUS));
|
||
}
|
||
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.
|
||
//
|
||
|
||
if (fLastService) {
|
||
SC_LOG0(TRACE,"RControlService:Forcing Service Shutdown\n");
|
||
ScRemoveService(serviceRecord);
|
||
}
|
||
}
|
||
}
|
||
return(status);
|
||
}
|
||
|
||
|
||
|
||
/****************************************************************************/
|
||
DWORD
|
||
ScCreateControlInstance (
|
||
OUT LPHANDLE PipeHandlePtr,
|
||
IN DWORD dwCurrentService,
|
||
IN PSID pAccountSid
|
||
)
|
||
|
||
/*++
|
||
|
||
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.
|
||
|
||
dwCurrentService - This is used to create a uniquely-named pipe
|
||
|
||
pAccountSid - The SID of the account that is allowed to access this pipe
|
||
|
||
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;
|
||
|
||
WCHAR wszPipeName[sizeof(CONTROL_PIPE_NAME) / sizeof(WCHAR) + PID_LEN] = CONTROL_PIPE_NAME;
|
||
|
||
SC_ACE_DATA AceData[1] = {
|
||
|
||
{ACCESS_ALLOWED_ACE_TYPE, 0, 0,
|
||
GENERIC_ALL, &pAccountSid}
|
||
|
||
};
|
||
|
||
//
|
||
// Generate the pipe name
|
||
//
|
||
_itow(dwCurrentService, wszPipeName + sizeof(CONTROL_PIPE_NAME) / sizeof(WCHAR) - 1, 10);
|
||
|
||
//
|
||
// Create a security descriptor for the control named pipe so
|
||
// that we can grant access to it solely to the service's account
|
||
//
|
||
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.
|
||
// Use FILE_FLAG_FIRST_PIPE_INSTANCE to make sure that we're the
|
||
// creator of the named pipe (vs. a malicious process that creates
|
||
// the pipe first and thereby gains access to the client service
|
||
// that connects to it).
|
||
//
|
||
|
||
*PipeHandlePtr = CreateNamedPipe (
|
||
wszPipeName,
|
||
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
|
||
PIPE_WAIT | PIPE_READMODE_MESSAGE | PIPE_TYPE_MESSAGE,
|
||
1, // one instance per process
|
||
8000,
|
||
sizeof(PIPE_RESPONSE_MSG),
|
||
CONTROL_TIMEOUT, // Default Timeout
|
||
&SecurityAttr); // Security Descriptor
|
||
|
||
status = NO_ERROR;
|
||
|
||
if (*PipeHandlePtr == INVALID_HANDLE_VALUE) {
|
||
status = GetLastError();
|
||
SC_LOG1(ERROR,
|
||
"CreateControlInstance: CreateNamedPipe failed, %ld\n",status);
|
||
}
|
||
|
||
(void) RtlDeleteSecurityObject(&SecurityDescriptor);
|
||
return(status);
|
||
}
|
||
|
||
|
||
/****************************************************************************/
|
||
DWORD
|
||
ScWaitForConnect (
|
||
IN HANDLE PipeHandle,
|
||
IN HANDLE hProcess OPTIONAL,
|
||
IN LPWSTR lpDisplayName,
|
||
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.
|
||
|
||
hProcess - The handle to the service process. We wait on this handle
|
||
and the pipe handle in case the process exits before the
|
||
pipe transaction times out.
|
||
|
||
lpDisplayName - The name of the service for which we're waiting.
|
||
|
||
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
|
||
|
||
CONST HANDLE phHandles[] = { PipeHandle, hProcess };
|
||
DWORD dwCount = (hProcess == NULL ? 1 : 2);
|
||
|
||
#if DBG
|
||
|
||
DWORD dwStartTick;
|
||
DWORD dwTotalTime;
|
||
|
||
#endif // DBG
|
||
|
||
|
||
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) {
|
||
|
||
#if DBG
|
||
|
||
dwStartTick = GetTickCount();
|
||
|
||
#endif // DBG
|
||
|
||
//
|
||
// Connection is pending
|
||
//
|
||
apiStatus = WaitForMultipleObjects(dwCount,
|
||
phHandles,
|
||
FALSE, // Wait for any
|
||
g_dwScPipeTransactTimeout);
|
||
|
||
#if DBG
|
||
|
||
dwTotalTime = GetTickCount() - dwStartTick;
|
||
|
||
if (dwTotalTime > SC_DEFAULT_PIPE_TRANSACT_TIMEOUT) {
|
||
|
||
SC_LOG1(ERROR,
|
||
"ScWaitForConnect: Wait on ConnectNamedPipe took %u milliseconds\n",
|
||
dwTotalTime);
|
||
}
|
||
|
||
#endif // DBG
|
||
|
||
if (apiStatus == WAIT_OBJECT_0) {
|
||
|
||
//
|
||
// Wait completed successfully -- the object that
|
||
// signalled was the pipe handle
|
||
//
|
||
status = GetOverlappedResult(
|
||
PipeHandle,
|
||
&overlapped,
|
||
&numBytesRead,
|
||
TRUE
|
||
);
|
||
|
||
if (status == FALSE) {
|
||
apiStatus = GetLastError();
|
||
|
||
SC_LOG(ERROR,
|
||
"ScWaitForConnect: GetOverlappedResult failed, rc=%lu\n",
|
||
apiStatus);
|
||
|
||
return apiStatus;
|
||
|
||
}
|
||
}
|
||
|
||
else {
|
||
|
||
//
|
||
// Either the connection timed out or the service process
|
||
// exited before calling StartServiceCtrlDispatcher
|
||
//
|
||
|
||
SC_LOG2(ERROR,
|
||
"ScWaitForConnect: Wait for connection for %u secs timed out --"
|
||
"service process DID %s exit\n",
|
||
g_dwScPipeTransactTimeout / 1000,
|
||
(apiStatus == WAIT_TIMEOUT ? "NOT" : ""));
|
||
|
||
//
|
||
// 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());
|
||
}
|
||
|
||
ScLogEvent(
|
||
NEVENT_CONNECTION_TIMEOUT,
|
||
g_dwScPipeTransactTimeout,
|
||
lpDisplayName
|
||
);
|
||
|
||
return ERROR_SERVICE_REQUEST_TIMEOUT;
|
||
}
|
||
}
|
||
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) {
|
||
|
||
#if DBG
|
||
|
||
dwStartTick = GetTickCount();
|
||
|
||
#endif // DBG
|
||
|
||
//
|
||
// Connection is pending
|
||
//
|
||
apiStatus = WaitForSingleObject(PipeHandle, g_dwScPipeTransactTimeout);
|
||
|
||
#if DBG
|
||
|
||
dwTotalTime = GetTickCount() - dwStartTick;
|
||
|
||
if (dwTotalTime > SC_DEFAULT_PIPE_TRANSACT_TIMEOUT) {
|
||
|
||
SC_LOG1(ERROR,
|
||
"ScWaitForConnect: Wait on ReadFile took %u milliseconds\n",
|
||
dwTotalTime);
|
||
}
|
||
|
||
#endif // DBG
|
||
|
||
if (apiStatus == WAIT_TIMEOUT) {
|
||
|
||
SC_LOG(ERROR,
|
||
"ScWaitForConnect: Wait for ReadFile for %u secs timed out\n",
|
||
g_dwScPipeTransactTimeout / 1000 );
|
||
|
||
//
|
||
// Cancel the named pipe operation.
|
||
//
|
||
status = CancelIo(PipeHandle);
|
||
|
||
if (status == FALSE) {
|
||
|
||
SC_LOG(ERROR, "ScWaitForConnect: CancelIo failed, %lu\n", GetLastError());
|
||
}
|
||
|
||
ScLogEvent(
|
||
NEVENT_READFILE_TIMEOUT,
|
||
g_dwScPipeTransactTimeout
|
||
);
|
||
|
||
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.dwDispatcherStatus;
|
||
|
||
return(NO_ERROR);
|
||
}
|
||
|
||
|
||
/****************************************************************************/
|
||
DWORD
|
||
ScValidatePnPService(
|
||
IN LPWSTR lpServiceName,
|
||
OUT SERVICE_STATUS_HANDLE *lphServiceStatus
|
||
)
|
||
{
|
||
DWORD dwError;
|
||
LPSERVICE_RECORD lpServiceRecord;
|
||
|
||
//
|
||
// Make sure PnP is supplying a valid OUT parameter
|
||
//
|
||
SC_ASSERT(lphServiceStatus != NULL);
|
||
|
||
//
|
||
// Validate the format of the service name.
|
||
//
|
||
if (! ScIsValidServiceName(lpServiceName))
|
||
{
|
||
return ERROR_INVALID_NAME;
|
||
}
|
||
|
||
//
|
||
// Find the service record in the database.
|
||
//
|
||
|
||
CServiceListSharedLock LLock;
|
||
|
||
dwError = ScGetNamedServiceRecord(lpServiceName,
|
||
&lpServiceRecord);
|
||
|
||
if (dwError != NO_ERROR)
|
||
{
|
||
return(dwError);
|
||
}
|
||
|
||
CServiceRecordSharedLock RLock;
|
||
|
||
//
|
||
// Make sure the service specified is the service
|
||
// that requested device notification
|
||
//
|
||
dwError = ScStatusAccessCheck(lpServiceRecord);
|
||
|
||
if (dwError == NO_ERROR)
|
||
{
|
||
*lphServiceStatus = (SERVICE_STATUS_HANDLE)lpServiceRecord;
|
||
}
|
||
|
||
return dwError;
|
||
}
|
||
|
||
|
||
DWORD
|
||
ScSendPnPMessage(
|
||
IN SERVICE_STATUS_HANDLE hServiceStatus,
|
||
IN DWORD OpCode,
|
||
IN DWORD dwEventType,
|
||
IN LPARAM EventData,
|
||
OUT LPDWORD lpdwHandlerRetVal
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is called by PnP when it needs to send a control to a
|
||
service that has requested notification. It simply packs the args and
|
||
calls ScSendControl.
|
||
|
||
Arguments:
|
||
|
||
OpCode - This is the opcode that is to be passed to the service.
|
||
|
||
dwEventType - The PnP event that has occurred
|
||
|
||
EventData - Pointer to other information the service may want
|
||
|
||
|
||
Return Value:
|
||
|
||
ERROR_SERVICE_NOT_ACTIVE - The named service has no image record
|
||
|
||
In addition, any value passed back from ScGetNamedImageRecord or ScSendControl
|
||
|
||
--*/
|
||
|
||
{
|
||
LPSERVICE_RECORD lpServiceRecord;
|
||
CONTROL_ARGS ControlArgs;
|
||
LPWSTR lpServiceName;
|
||
LPWSTR lpDisplayName;
|
||
HANDLE hPipe;
|
||
|
||
//
|
||
// Make sure this handle is valid (in case the service in question
|
||
// was deleted and was signed up for device notifications)
|
||
//
|
||
// BUGBUG -- We should call a PnP callback and have it free up the
|
||
// node for the service in question if it's deleted
|
||
//
|
||
|
||
SC_ASSERT(((LPSERVICE_RECORD) hServiceStatus)->Signature == SERVICE_SIGNATURE);
|
||
|
||
//
|
||
// Make sure PnP is giving us a valid OUT parameter
|
||
//
|
||
SC_ASSERT(lpdwHandlerRetVal != NULL);
|
||
|
||
lpServiceRecord = (LPSERVICE_RECORD)hServiceStatus;
|
||
|
||
{
|
||
//
|
||
// Get the information we need then release the lock
|
||
//
|
||
CServiceRecordSharedLock RLock;
|
||
|
||
if (lpServiceRecord->ImageRecord == NULL)
|
||
{
|
||
return ERROR_SERVICE_NOT_ACTIVE;
|
||
}
|
||
|
||
lpServiceName = lpServiceRecord->ServiceName;
|
||
lpDisplayName = lpServiceRecord->DisplayName;
|
||
hPipe = lpServiceRecord->ImageRecord->PipeHandle;
|
||
}
|
||
|
||
//
|
||
// If it's a PnP device event, pass along the arguments for the named pipe
|
||
//
|
||
switch (OpCode) {
|
||
|
||
case SERVICE_CONTROL_DEVICEEVENT:
|
||
|
||
//
|
||
// Make sure that either both the size and buffer are
|
||
// 0 and NULL or that they're non-zero and non-NULL
|
||
//
|
||
SC_ASSERT(((PDEV_BROADCAST_HDR)EventData)->dbch_size && EventData ||
|
||
!((PDEV_BROADCAST_HDR)EventData)->dbch_size && !EventData);
|
||
|
||
ControlArgs.PnPArgs.dwEventType = dwEventType;
|
||
ControlArgs.PnPArgs.dwEventDataSize = ((PDEV_BROADCAST_HDR)EventData)->dbch_size;
|
||
ControlArgs.PnPArgs.EventData = (VOID *)EventData;
|
||
|
||
break;
|
||
|
||
case SERVICE_CONTROL_POWEREVENT:
|
||
case SERVICE_CONTROL_HARDWAREPROFILECHANGE:
|
||
|
||
//
|
||
// Hardware Profile Change and Power messages have no LPARAM.
|
||
// They just tell a service that something noteworthy has happened.
|
||
//
|
||
SC_ASSERT(EventData == NULL);
|
||
|
||
ControlArgs.PnPArgs.dwEventType = dwEventType;
|
||
ControlArgs.PnPArgs.dwEventDataSize = 0;
|
||
ControlArgs.PnPArgs.EventData = NULL;
|
||
break;
|
||
|
||
|
||
default:
|
||
SC_ASSERT(FALSE);
|
||
break;
|
||
}
|
||
|
||
return ScSendControl(lpServiceName,
|
||
lpDisplayName,
|
||
hPipe,
|
||
OpCode,
|
||
&ControlArgs,
|
||
3, // 3 PnP arguments
|
||
lpdwHandlerRetVal);
|
||
}
|
||
|
||
|
||
DWORD
|
||
RI_ScSendTSMessage (
|
||
IN SC_RPC_HANDLE hSCManager,
|
||
IN DWORD OpCode,
|
||
IN DWORD dwEvent,
|
||
IN DWORD cbData,
|
||
IN LPBYTE lpData
|
||
)
|
||
{
|
||
LPSERVICE_RECORD lpServiceRecord;
|
||
CONTROL_ARGS ControlArgs;
|
||
DWORD dwAcceptedMask;
|
||
DWORD dwError;
|
||
DWORD dwNumServices = 0;
|
||
DWORD i;
|
||
LPTS_CONTROL_INFO lpControlInfo;
|
||
|
||
lpServiceRecord = NULL;
|
||
|
||
ControlArgs.PnPArgs.dwEventType = dwEvent;
|
||
ControlArgs.PnPArgs.dwEventDataSize = cbData;
|
||
ControlArgs.PnPArgs.EventData = lpData;
|
||
|
||
if (OpCode != SERVICE_CONTROL_SESSIONCHANGE)
|
||
{
|
||
ASSERT(OpCode == SERVICE_CONTROL_SESSIONCHANGE);
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
|
||
dwAcceptedMask = SERVICE_ACCEPT_SESSIONCHANGE;
|
||
|
||
//
|
||
// Make sure that the caller is LocalSystem
|
||
//
|
||
|
||
dwError = ScStatusAccessCheck(NULL);
|
||
|
||
if (dwError != NO_ERROR)
|
||
{
|
||
SC_LOG1(ERROR,
|
||
"RI_ScSendTSMessage: ScStatusAccessCheck failed %d\n",
|
||
dwError);
|
||
|
||
return dwError;
|
||
}
|
||
|
||
{
|
||
CServiceListSharedLock LLock;
|
||
CServiceRecordExclusiveLock RLock;
|
||
|
||
//
|
||
// Go through the list and copy out the pointer to the service name and
|
||
// the pipe handle for each service that wants to receive this control.
|
||
// We do this so we can call ScSendControl for each service afterwards
|
||
// without holding the locks required to traverse the list. Note that
|
||
// it's conceivable that the service name will be junk by the time
|
||
// ScSendControl is called (if the service is deleted before the call)
|
||
// but if that's the case, the pipe handle will be invalid, causing the
|
||
// call to fail gracefully.
|
||
//
|
||
|
||
FOR_SERVICES_THAT(lpServiceRecord,
|
||
(lpServiceRecord->ServiceStatus.dwServiceType & SERVICE_WIN32) &&
|
||
(lpServiceRecord->ServiceStatus.dwCurrentState != SERVICE_STOPPED) &&
|
||
(lpServiceRecord->ServiceStatus.dwCurrentState != SERVICE_STOP_PENDING) &&
|
||
(lpServiceRecord->ServiceStatus.dwControlsAccepted & dwAcceptedMask) &&
|
||
(lpServiceRecord->ImageRecord != NULL)
|
||
)
|
||
{
|
||
dwNumServices++;
|
||
}
|
||
|
||
if (dwNumServices == 0)
|
||
{
|
||
//
|
||
// No services accept the control
|
||
//
|
||
return NO_ERROR;
|
||
}
|
||
|
||
lpControlInfo = (LPTS_CONTROL_INFO) LocalAlloc(LMEM_FIXED,
|
||
dwNumServices * sizeof(TS_CONTROL_INFO));
|
||
|
||
if (lpControlInfo == NULL)
|
||
{
|
||
return ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
|
||
dwNumServices = 0;
|
||
|
||
FOR_SERVICES_THAT(lpServiceRecord,
|
||
(lpServiceRecord->ServiceStatus.dwServiceType & SERVICE_WIN32) &&
|
||
(lpServiceRecord->ServiceStatus.dwCurrentState != SERVICE_STOPPED) &&
|
||
(lpServiceRecord->ServiceStatus.dwCurrentState != SERVICE_STOP_PENDING) &&
|
||
(lpServiceRecord->ServiceStatus.dwControlsAccepted & dwAcceptedMask) &&
|
||
(lpServiceRecord->ImageRecord != NULL)
|
||
)
|
||
{
|
||
//
|
||
// Copy out the information we need
|
||
//
|
||
|
||
lpControlInfo[dwNumServices].lpServiceName = lpServiceRecord->ServiceName;
|
||
lpControlInfo[dwNumServices].lpDisplayName = lpServiceRecord->DisplayName;
|
||
lpControlInfo[dwNumServices].hPipe = lpServiceRecord->ImageRecord->PipeHandle;
|
||
dwNumServices++;
|
||
}
|
||
}
|
||
|
||
for (i = 0; i < dwNumServices; i++)
|
||
{
|
||
//
|
||
// Send the control
|
||
//
|
||
|
||
dwError = ScSendControl(lpControlInfo[i].lpServiceName,
|
||
lpControlInfo[i].lpDisplayName,
|
||
lpControlInfo[i].hPipe,
|
||
OpCode,
|
||
&ControlArgs,
|
||
3,
|
||
NULL);
|
||
|
||
//
|
||
// Ignore any errors sending the control
|
||
//
|
||
|
||
if (dwError != NO_ERROR)
|
||
{
|
||
SC_LOG2(ERROR,
|
||
"RI_ScSendTSMessage: Error %d sending control to %ws service\n",
|
||
dwError,
|
||
lpControlInfo[i].lpServiceName);
|
||
}
|
||
}
|
||
|
||
LocalFree(lpControlInfo);
|
||
|
||
return NO_ERROR;
|
||
}
|
||
|
||
|
||
/****************************************************************************/
|
||
DWORD
|
||
ScSendControl(
|
||
IN LPWSTR ServiceName,
|
||
IN LPWSTR DisplayName,
|
||
IN HANDLE PipeHandle,
|
||
IN DWORD OpCode,
|
||
IN LPCONTROL_ARGS lpControlArgs OPTIONAL,
|
||
IN DWORD NumArgs,
|
||
OUT LPDWORD lpdwHandlerRetVal 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.
|
||
|
||
lpControlArgs - This is an optional pointer to a CONTROL_ARGS union,
|
||
which can contain either an array of pointers to NUL-terminated
|
||
strings (for the SERVICE_CONTROL_START case) or a structure that
|
||
holds Plug-and-Play arguments (for the SERVICE_CONTROL_DEVICEEVENT,
|
||
SERVICE_CONTROL_POWEREVENT, SERVICE_CONTROL_HARDWAREPROFILECHANGE,
|
||
and SERVICE_CONTROL_SESSIONCHANGE cases).
|
||
|
||
NumArgs - This indicates how many arguments are in the structure
|
||
lpControlArgs contains. For SERVICE_CONTROL_START, it's the number
|
||
of strings in the argument array. For Plug-and-Play events, it's
|
||
the number of arguments in a Plug-and-Play message (currently 3)
|
||
|
||
lpdwHandlerRetVal - The return value from the Service's control handler
|
||
|
||
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;
|
||
LPCTRL_MSG_HEADER lpcmhBuffer; // Message buffer
|
||
DWORD serviceNameSize;
|
||
DWORD sendBufferSize;
|
||
BOOL status;
|
||
PIPE_RESPONSE_MSG serviceResponseBuffer;
|
||
DWORD bytesRead;
|
||
DWORD i;
|
||
|
||
static OVERLAPPED overlapped={0,0,0,0,0}; // overlapped structure to implement
|
||
// timeout on TransactNamedPipe
|
||
// static because on shutdown, we'll
|
||
// be firing off a bunch of async
|
||
// writes, which write to the overlapped
|
||
// structure upon completion, and we
|
||
// don't want them trashing the stack
|
||
|
||
if (ARGUMENT_PRESENT(lpdwHandlerRetVal))
|
||
{
|
||
//
|
||
// Initialize the OUT pointer
|
||
//
|
||
*lpdwHandlerRetVal = NO_ERROR;
|
||
}
|
||
|
||
serviceNameSize = (DWORD) WCSSIZE(ServiceName);
|
||
sendBufferSize = serviceNameSize + sizeof(CTRL_MSG_HEADER);
|
||
|
||
//
|
||
// Add an extra PVOID size to help settle alignment problems that
|
||
// may occur when the array of pointers follows the service name string.
|
||
//
|
||
sendBufferSize += sizeof(PVOID);
|
||
|
||
//
|
||
// There are control arguments, so add their size to the buffer size
|
||
//
|
||
if (lpControlArgs != NULL) {
|
||
|
||
if (OpCode == SERVICE_CONTROL_START_SHARE ||
|
||
OpCode == SERVICE_CONTROL_START_OWN) {
|
||
|
||
//
|
||
// Service is starting
|
||
//
|
||
for (i = 0; i < NumArgs; i++) {
|
||
sendBufferSize += (DWORD) WCSSIZE(lpControlArgs->CmdArgs[i]) +
|
||
sizeof(LPWSTR);
|
||
}
|
||
}
|
||
else {
|
||
|
||
SC_ASSERT(OpCode == SERVICE_CONTROL_DEVICEEVENT ||
|
||
OpCode == SERVICE_CONTROL_HARDWAREPROFILECHANGE ||
|
||
OpCode == SERVICE_CONTROL_POWEREVENT ||
|
||
OpCode == SERVICE_CONTROL_SESSIONCHANGE);
|
||
|
||
//
|
||
// PnP event
|
||
//
|
||
sendBufferSize += sizeof(lpControlArgs->PnPArgs.dwEventType);
|
||
sendBufferSize += sizeof(lpControlArgs->PnPArgs.dwEventDataSize);
|
||
|
||
sendBufferSize += lpControlArgs->PnPArgs.dwEventDataSize;
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Allocate the buffer and set a pointer to it that knows the structure
|
||
// of the header.
|
||
//
|
||
lpcmhBuffer = (LPCTRL_MSG_HEADER)LocalAlloc(LMEM_ZEROINIT, sendBufferSize);
|
||
|
||
if (lpcmhBuffer == NULL) {
|
||
SC_LOG(TRACE,"SendControl:LocalAlloc failed, rc=%d/n", GetLastError());
|
||
return (ERROR_NOT_ENOUGH_MEMORY);
|
||
}
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// Marshall the data into the buffer.
|
||
//
|
||
//
|
||
// The Control Message looks like this for service start:
|
||
// CTRL_MSG_HEADER Header
|
||
// WCHAR ServiceName[?]
|
||
// LPWSTR Argv0 offset
|
||
// LPWSTR Argv1 offset
|
||
// LPWSTR Argv2 offset
|
||
// LPWSTR ...
|
||
// WCHAR Argv0[?]
|
||
// WCHAR Argv1[?]
|
||
// WCHAR Argv2[?]
|
||
// WCHAR ...
|
||
//
|
||
// and like this for PNP events:
|
||
// CTRL_MSG_HEADER Header
|
||
// WCHAR ServiceName[?]
|
||
// DWORD wParam
|
||
// BYTE lParam[?]
|
||
//
|
||
|
||
lpcmhBuffer->OpCode = OpCode;
|
||
lpcmhBuffer->Count = sendBufferSize;
|
||
|
||
//
|
||
// Copy the service name to buffer and store the offset.
|
||
//
|
||
|
||
lpcmhBuffer->ServiceNameOffset = sizeof(CTRL_MSG_HEADER);
|
||
wcscpy((LPWSTR)(lpcmhBuffer + 1), ServiceName);
|
||
|
||
//
|
||
// Pack message-specific arguments as necessary
|
||
//
|
||
switch (OpCode) {
|
||
|
||
case SERVICE_CONTROL_START_SHARE:
|
||
case SERVICE_CONTROL_START_OWN:
|
||
|
||
if (NumArgs > 0) {
|
||
|
||
//
|
||
// Service start -- Determine the offset from the top of the argv
|
||
// array to the first argv string. Also determine the pointer value
|
||
// for that location.
|
||
//
|
||
DWORD dwOffset = NumArgs * sizeof(LPWSTR);
|
||
LPWSTR *ppwszArgs;
|
||
|
||
//
|
||
// Calculate the beginning of the string area and the beginning
|
||
// of the arg vector area. Align the vector pointer on a PVOID
|
||
// boundary.
|
||
//
|
||
|
||
ppwszArgs = (LPWSTR *)((LPBYTE)(lpcmhBuffer + 1) + serviceNameSize);
|
||
ppwszArgs = (LPWSTR *)ROUND_UP_POINTER(ppwszArgs, sizeof(PVOID));
|
||
|
||
lpcmhBuffer->ArgvOffset = (DWORD)((LPBYTE)ppwszArgs - (LPBYTE)lpcmhBuffer);
|
||
lpcmhBuffer->NumCmdArgs = NumArgs;
|
||
|
||
//
|
||
// 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.
|
||
//
|
||
for (i = 0; i < NumArgs; i++) {
|
||
|
||
wcscpy((LPWSTR) ((LPBYTE)ppwszArgs + dwOffset),
|
||
lpControlArgs->CmdArgs[i]);
|
||
|
||
ppwszArgs[i] = (LPWSTR)(DWORD_PTR) dwOffset;
|
||
dwOffset += (DWORD) WCSSIZE(lpControlArgs->CmdArgs[i]);
|
||
}
|
||
}
|
||
break;
|
||
|
||
case SERVICE_CONTROL_DEVICEEVENT:
|
||
case SERVICE_CONTROL_HARDWAREPROFILECHANGE:
|
||
case SERVICE_CONTROL_POWEREVENT:
|
||
case SERVICE_CONTROL_SESSIONCHANGE:
|
||
{
|
||
LPDWORD lpdwArgLocation;
|
||
|
||
//
|
||
// Calculate the location for the PnP/power arguments.
|
||
// Align the pointer on a PVOID boundary.
|
||
//
|
||
lpdwArgLocation = (LPDWORD)((LPBYTE)(lpcmhBuffer + 1) + serviceNameSize);
|
||
lpdwArgLocation = (LPDWORD)ROUND_UP_POINTER(lpdwArgLocation, sizeof(PVOID));
|
||
|
||
lpcmhBuffer->ArgvOffset = (DWORD)((LPBYTE)lpdwArgLocation - (LPBYTE)lpcmhBuffer);
|
||
|
||
//
|
||
// Copy the wParam into the buffer
|
||
//
|
||
*lpdwArgLocation = lpControlArgs->PnPArgs.dwEventType;
|
||
|
||
//
|
||
// Copy the lParam into the buffer
|
||
//
|
||
RtlCopyMemory(lpdwArgLocation + 1,
|
||
lpControlArgs->PnPArgs.EventData,
|
||
lpControlArgs->PnPArgs.dwEventDataSize);
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
// If the SCM is sending a shutdown message to a service, we want to just
|
||
// use WriteFile instead of TransactNamedPipe, since a badly behaved service
|
||
// that doesn't write back to the pipe leaves the SCM hanging and unable to
|
||
// properly shut down the remaining services
|
||
|
||
SC_LOG0(LOCKS,"SendControl: Entering TransactPipe Critical Section.\n");
|
||
EnterCriticalSection(&ScTransactNPCriticalSection);
|
||
|
||
if (OpCode == SERVICE_CONTROL_SHUTDOWN) {
|
||
|
||
SC_LOG0(SHUTDOWN,
|
||
"ScSendControl: Using WriteFile to send a service shutdown message.\n");
|
||
|
||
status = WriteFile(
|
||
PipeHandle,
|
||
lpcmhBuffer,
|
||
sendBufferSize,
|
||
&bytesRead,
|
||
&overlapped);
|
||
|
||
if (status || (returnStatus = GetLastError()) == ERROR_IO_PENDING) {
|
||
returnStatus = NO_ERROR;
|
||
}
|
||
|
||
SC_LOG(SHUTDOWN, "ScSendControl returning with code %d\n", returnStatus);
|
||
}
|
||
else {
|
||
//
|
||
// The parameters are marshalled, now send the buffer and wait for
|
||
// response.
|
||
//
|
||
|
||
SC_LOG(TRACE,"SendControl: Sending a TransactMessage.\n",0);
|
||
|
||
returnStatus = NO_ERROR;
|
||
status = TransactNamedPipe(PipeHandle,
|
||
lpcmhBuffer,
|
||
sendBufferSize,
|
||
&serviceResponseBuffer,
|
||
sizeof(PIPE_RESPONSE_MSG),
|
||
&bytesRead,
|
||
&overlapped);
|
||
|
||
if (status == FALSE) {
|
||
|
||
returnStatus = GetLastError();
|
||
if (returnStatus == ERROR_PIPE_BUSY) {
|
||
SC_LOG(ERROR, "Cleaning out pipe for %ws service\n", ServiceName);
|
||
ScCleanOutPipe(PipeHandle);
|
||
status = TRUE;
|
||
returnStatus = NO_ERROR;
|
||
status = TransactNamedPipe(PipeHandle,
|
||
lpcmhBuffer,
|
||
sendBufferSize,
|
||
&serviceResponseBuffer,
|
||
sizeof(PIPE_RESPONSE_MSG),
|
||
&bytesRead,
|
||
&overlapped);
|
||
|
||
if (status == FALSE) {
|
||
returnStatus = GetLastError();
|
||
}
|
||
}
|
||
}
|
||
|
||
if (status == FALSE) {
|
||
if (returnStatus != ERROR_IO_PENDING) {
|
||
|
||
SC_LOG2(ERROR,
|
||
"SendControl:TransactNamedPipe to %ws service failed, rc=%lu\n",
|
||
ServiceName,
|
||
returnStatus);
|
||
|
||
goto CleanUp;
|
||
|
||
} else {
|
||
|
||
#if DBG
|
||
|
||
DWORD dwStartTick = GetTickCount();
|
||
DWORD dwTotalTime;
|
||
|
||
#endif // DBG
|
||
|
||
//
|
||
// Transaction is pending
|
||
//
|
||
status = WaitForSingleObject(PipeHandle, g_dwScPipeTransactTimeout);
|
||
|
||
#if DBG
|
||
|
||
dwTotalTime = GetTickCount() - dwStartTick;
|
||
|
||
if (dwTotalTime > SC_DEFAULT_PIPE_TRANSACT_TIMEOUT) {
|
||
|
||
SC_LOG3(ERROR,
|
||
"ScSendControl: Pipe transaction to service %ws on "
|
||
"control %u took %u milliseconds\n",
|
||
ServiceName,
|
||
OpCode,
|
||
dwTotalTime);
|
||
}
|
||
|
||
#endif // DBG
|
||
|
||
if (status == WAIT_TIMEOUT) {
|
||
|
||
SC_LOG2(ERROR,
|
||
"SendControl: Wait on transact to %ws service for %u millisecs timed out\n",
|
||
ServiceName, g_dwScPipeTransactTimeout);
|
||
|
||
//
|
||
// Cancel the named pipe operation.
|
||
// NOTE: CancelIo cancels ALL pending I/O operations issued by
|
||
// this thread on the PipeHandle. Since the service controller
|
||
// functions do nothing but wait after starting asynchronous
|
||
// named pipe operations, there should be no other operations.
|
||
//
|
||
status = CancelIo(PipeHandle);
|
||
|
||
if (status == FALSE) {
|
||
|
||
SC_LOG(ERROR, "SendControl: CancelIo failed, %lu\n", GetLastError());
|
||
}
|
||
|
||
ScLogEvent(
|
||
NEVENT_TRANSACT_TIMEOUT,
|
||
g_dwScPipeTransactTimeout,
|
||
ServiceName
|
||
);
|
||
|
||
returnStatus = ERROR_SERVICE_REQUEST_TIMEOUT;
|
||
goto CleanUp;
|
||
|
||
} else if (status == 0) {
|
||
|
||
//
|
||
// Wait completed successfully
|
||
//
|
||
status = GetOverlappedResult(
|
||
PipeHandle,
|
||
&overlapped,
|
||
&bytesRead,
|
||
TRUE
|
||
);
|
||
|
||
if (status == FALSE) {
|
||
returnStatus = GetLastError();
|
||
SC_LOG(ERROR,
|
||
"SendControl: GetOverlappedResult failed, rc=%lu\n",
|
||
returnStatus);
|
||
goto CleanUp;
|
||
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Response received from the control dispatcher
|
||
//
|
||
if (bytesRead != sizeof(PIPE_RESPONSE_MSG)) {
|
||
|
||
//
|
||
// Successful transact, but we didn't get proper input.
|
||
// (note: we should never receive more bytes unless there
|
||
// is a bug in TransactNamedPipe).
|
||
//
|
||
|
||
SC_LOG(ERROR,
|
||
"SendControl: Incorrect num bytes in response, num=%d",
|
||
bytesRead);
|
||
|
||
ScLogEvent(NEVENT_TRANSACT_INVALID);
|
||
|
||
returnStatus = ERROR_GEN_FAILURE;
|
||
}
|
||
else {
|
||
returnStatus = serviceResponseBuffer.dwDispatcherStatus;
|
||
|
||
if (ARGUMENT_PRESENT(lpdwHandlerRetVal)) {
|
||
*lpdwHandlerRetVal = serviceResponseBuffer.dwHandlerRetVal;
|
||
}
|
||
}
|
||
}
|
||
|
||
CleanUp:
|
||
SC_LOG(LOCKS,"SendControl: Leaving TransactPipe Critical Section.\n",0);
|
||
|
||
LeaveCriticalSection(&ScTransactNPCriticalSection);
|
||
|
||
LocalFree(lpcmhBuffer);
|
||
|
||
if (returnStatus == NO_ERROR
|
||
&&
|
||
IS_CONTROL_LOGGABLE(OpCode)
|
||
&&
|
||
DisplayName != NULL && DisplayName[0] != L'\0')
|
||
{
|
||
ScLogControlEvent(NEVENT_SERVICE_CONTROL_SUCCESS,
|
||
DisplayName,
|
||
OpCode);
|
||
}
|
||
|
||
return(returnStatus);
|
||
}
|
||
|
||
|
||
VOID
|
||
ScInitTransactNamedPipe(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function initializes the Critical Section that serializes
|
||
calls to TransactNamedPipe and sets the pipe timeout value.
|
||
|
||
Arguments:
|
||
|
||
none
|
||
|
||
Return Value:
|
||
|
||
none
|
||
|
||
--*/
|
||
{
|
||
DWORD dwStatus;
|
||
HKEY hKeyControl;
|
||
DWORD dwValueType;
|
||
DWORD dwBufSize = sizeof(DWORD);
|
||
|
||
|
||
InitializeCriticalSection(&ScTransactNPCriticalSection);
|
||
|
||
//
|
||
// Read the pipe timeout value from the registry if it exists.
|
||
// If the read fails or the value's not there, use the default.
|
||
//
|
||
dwStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, // hKey
|
||
REGKEY_PIPE_TIMEOUT, // lpSubKey
|
||
0,
|
||
KEY_READ, // Read access
|
||
&hKeyControl); // Newly Opened Key Handle
|
||
|
||
if (dwStatus == NO_ERROR) {
|
||
|
||
dwStatus = RegQueryValueEx(hKeyControl,
|
||
REGVAL_PIPE_TIMEOUT,
|
||
NULL,
|
||
&dwValueType,
|
||
(LPBYTE)&g_dwScPipeTransactTimeout,
|
||
&dwBufSize);
|
||
|
||
if (dwStatus != NO_ERROR || dwValueType != REG_DWORD) {
|
||
|
||
//
|
||
// The value's either not there or bogus so just use the default
|
||
//
|
||
SC_LOG0(TRACE,
|
||
"ScInitTransactNamedPipe: Can't find ServicesPipeTimeout "
|
||
"value in registry\n");
|
||
}
|
||
|
||
RegCloseKey(hKeyControl);
|
||
}
|
||
else {
|
||
|
||
//
|
||
// Not an error for this function, although a missing control
|
||
// key is probably relatively bad, so notify everybody
|
||
//
|
||
SC_LOG0(ERROR,
|
||
"ScInitTransactNamedPipe: Can't find Control "
|
||
"key in registry!\n");
|
||
}
|
||
|
||
SC_LOG1(TRACE,
|
||
"ScInitTransactNamedPipe: Using pipe timeout value of %u\n",
|
||
g_dwScPipeTransactTimeout);
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
ScShutdownAllServices(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
(called at system shutdown).
|
||
This function sends shutdown requests to all services that have
|
||
registered an interest in shutdown notification.
|
||
|
||
When we leave this routine, to the best of our knowledge, all the
|
||
services that should stop have stopped - or are in some hung state.
|
||
|
||
Note: It is expected that the RPC entry points are no longer serviced,
|
||
so we should not be receiving any requests that will add or delete
|
||
service records. Therefore, locks are not used when reading service
|
||
records during the shutdown loop.
|
||
|
||
|
||
Arguments:
|
||
|
||
none
|
||
|
||
Return Value:
|
||
|
||
none
|
||
|
||
Note:
|
||
|
||
|
||
--*/
|
||
|
||
{
|
||
DWORD status;
|
||
LPSERVICE_RECORD *affectedServices;
|
||
DWORD serviceIndex = 0;
|
||
DWORD arrayEnd = 0;
|
||
BOOL ServicesStopping;
|
||
DWORD maxWait = 0;
|
||
DWORD startTime;
|
||
DWORD arraySize;
|
||
|
||
#ifdef SC_SHUTDOWN_METRIC
|
||
|
||
//
|
||
// Local variables for shutdown performance metrics
|
||
//
|
||
DWORD dwShutdownTimeout = 0;
|
||
HKEY hKeyControl;
|
||
DWORD dwValueType;
|
||
DWORD dwBufSize = sizeof(DWORD);
|
||
|
||
#endif // SC_SHUTDOWN_METRIC
|
||
|
||
|
||
//
|
||
// Allocate a temporary array of services which we're interested in.
|
||
// (This is purely an optimization to avoid repeated traversals of the
|
||
// entire database of installed services.)
|
||
//
|
||
CServiceListSharedLock LLock;
|
||
arraySize = ScGetTotalNumberOfRecords();
|
||
|
||
affectedServices = (LPSERVICE_RECORD *)LocalAlloc(
|
||
LMEM_FIXED,
|
||
arraySize * sizeof(LPSERVICE_RECORD));
|
||
|
||
if (affectedServices == NULL) {
|
||
SC_LOG0(ERROR,"ScShutdownAllServices: LocalAlloc Failed\n");
|
||
return;
|
||
}
|
||
|
||
#ifdef SC_SHUTDOWN_METRIC
|
||
|
||
//
|
||
// Read the shutdown timeout value from the registry if it exists.
|
||
// If the read fails or the value's not there, use the default.
|
||
//
|
||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, // hKey
|
||
REGKEY_SHUTDOWN_TIMEOUT, // lpSubKey
|
||
0,
|
||
KEY_READ, // Read access
|
||
&hKeyControl) // Handle to newly-opened key
|
||
|
||
== ERROR_SUCCESS)
|
||
{
|
||
RegQueryValueEx(hKeyControl,
|
||
REGVAL_SHUTDOWN_TIMEOUT,
|
||
NULL,
|
||
&dwValueType,
|
||
(LPBYTE)&dwShutdownTimeout,
|
||
&dwBufSize);
|
||
|
||
RegCloseKey(hKeyControl);
|
||
}
|
||
|
||
#endif // SC_SHUTDOWN_METRIC
|
||
|
||
//-------------------------------------------------------------------
|
||
//
|
||
// Loop through service list sending stop requests to all that
|
||
// should receive such requests.
|
||
//
|
||
//-------------------------------------------------------------------
|
||
SC_LOG0(SHUTDOWN,"***** BEGIN SENDING STOPS TO SERVICES *****\n");
|
||
|
||
FOR_SERVICES_THAT(Service,
|
||
(Service->ServiceStatus.dwServiceType & SERVICE_WIN32) &&
|
||
(Service->ServiceStatus.dwCurrentState != SERVICE_STOPPED) &&
|
||
(Service->ServiceStatus.dwCurrentState != SERVICE_STOP_PENDING) &&
|
||
(Service->ServiceStatus.dwControlsAccepted & SERVICE_ACCEPT_SHUTDOWN) &&
|
||
(Service->ImageRecord != NULL)
|
||
)
|
||
{
|
||
//
|
||
// If the service is not in the stopped or stop pending
|
||
// state, it should be ok to send the control.
|
||
//
|
||
SC_LOG1(SHUTDOWN,"Shutdown: Sending Stop to Service : %ws\n",
|
||
Service->ServiceName);
|
||
|
||
status = ScSendControl(Service->ServiceName,
|
||
Service->DisplayName,
|
||
Service->ImageRecord->PipeHandle,
|
||
SERVICE_CONTROL_SHUTDOWN,
|
||
NULL, // CmdArgs
|
||
0L, // NumArgs
|
||
NULL); // Ignore handler return value
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
SC_LOG0(SHUTDOWN,"***** DONE SENDING STOPS TO SERVICES *****\n");
|
||
|
||
//-------------------------------------------------------------------
|
||
//
|
||
// Now check to see if these services stopped.
|
||
//
|
||
//-------------------------------------------------------------------
|
||
|
||
startTime = GetTickCount();
|
||
arrayEnd = serviceIndex;
|
||
ServicesStopping = (serviceIndex != 0);
|
||
|
||
SC_LOG(SHUTDOWN,"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(SHUTDOWN,
|
||
"%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:
|
||
break;
|
||
|
||
case SERVICE_RUNNING:
|
||
|
||
if (maxWait < 30000) {
|
||
maxWait = 30000;
|
||
}
|
||
|
||
SC_LOG2(SHUTDOWN, "%ws Service is still running, maxWait is %lu\n",
|
||
Service->ServiceName, maxWait);
|
||
|
||
ServicesStopping = TRUE;
|
||
break;
|
||
|
||
default:
|
||
//
|
||
// This is an error. But we can't do anything about
|
||
// it, so it will be ignored.
|
||
//
|
||
SC_LOG2(SHUTDOWN,"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) {
|
||
|
||
#ifdef SC_SHUTDOWN_METRIC
|
||
|
||
//
|
||
// Performance hook for service shutdown -- if a shutdown
|
||
// timeout was specified in the registry and it's been
|
||
// longer than the timeout, print out a list of all the
|
||
// unstopped services and break into the debugger. Do this
|
||
// on both free and checked builds. Note that this must be
|
||
// #if'ed out prior to shipping NT 5.0.
|
||
//
|
||
if (dwShutdownTimeout != 0
|
||
&&
|
||
(GetTickCount() - startTime) > dwShutdownTimeout) {
|
||
|
||
//
|
||
// Uh oh -- we've exceeded the max shutdown time
|
||
//
|
||
DbgPrint("\n[SC-SHUTDOWN] The following services failed to "
|
||
"shut down in %lu seconds:\n",
|
||
dwShutdownTimeout / 1000);
|
||
|
||
for (serviceIndex = 0; serviceIndex < arrayEnd; serviceIndex++) {
|
||
|
||
Service = affectedServices[serviceIndex];
|
||
|
||
if (Service->ServiceStatus.dwCurrentState != SERVICE_STOPPED) {
|
||
|
||
DbgPrint("%ws service is still running (state = %lu)\n",
|
||
Service->ServiceName,
|
||
Service->ServiceStatus.dwCurrentState);
|
||
}
|
||
}
|
||
|
||
DebugBreak();
|
||
}
|
||
|
||
#endif // SC_SHUTDOWN_METRIC
|
||
|
||
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(ERROR,
|
||
"The Services didn't stop within the timeout "
|
||
"period of %lu.\n --- There is still at least one "
|
||
"service running\n",
|
||
maxWait);
|
||
|
||
#if DBG
|
||
SC_LOG0(ERROR, "The following Services failed to shut down:\n");
|
||
|
||
for (serviceIndex = 0; serviceIndex < arrayEnd ; serviceIndex++) {
|
||
|
||
Service = affectedServices[serviceIndex];
|
||
|
||
if (Service->ServiceStatus.dwCurrentState != SERVICE_STOPPED) {
|
||
|
||
SC_LOG2(ERROR, "%ws Service is still running (Service state = %lu)\n",
|
||
Service->ServiceName, Service->ServiceStatus.dwCurrentState);
|
||
}
|
||
}
|
||
#endif // DBG
|
||
|
||
ServicesStopping = FALSE;
|
||
}
|
||
}
|
||
}
|
||
SC_LOG0(SHUTDOWN,"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);
|
||
}
|