1013 lines
30 KiB
C++
1013 lines
30 KiB
C++
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1991 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
status.cxx
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
This file contains functions that are involved with setting the
|
|||
|
status for a service in the service controller.
|
|||
|
|
|||
|
RSetServiceStatus
|
|||
|
RemovalThread
|
|||
|
RI_ScSetServiceBitsA
|
|||
|
RI_ScSetServiceBitsW
|
|||
|
ScRemoveServiceBits
|
|||
|
ScInitServerAnnounceFcn
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Dan Lafferty (danl) 20-Mar-1991
|
|||
|
|
|||
|
Environment:
|
|||
|
|
|||
|
User Mode -Win32
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
10-Mar-1998 jschwart
|
|||
|
Add code to RSetServiceStatus to notify Plug-and-Play when a service
|
|||
|
registers/deregisters for hardware profile change notifications.
|
|||
|
|
|||
|
08-Jan-1997 anirudhs
|
|||
|
RSetServiceStatus: Fix obscure locking bugs found by the new locking
|
|||
|
scheme. When a service stops, we sometimes need more restrictive
|
|||
|
locks than was previously assumed.
|
|||
|
|
|||
|
11-Apr-1996 anirudhs
|
|||
|
RSetServiceStatus: Notify NDIS when a service that belongs to a
|
|||
|
group NDIS is interested in starts running.
|
|||
|
|
|||
|
21-Nov-1995 anirudhs
|
|||
|
RI_ScSetServiceBitsW: Catch access violations caused if the
|
|||
|
hServiceStatus parameter is invalid.
|
|||
|
|
|||
|
23-Mar-1994 danl
|
|||
|
RSetServiceStatus: Only set the PopupStartFail flag when we have
|
|||
|
actually logged an event. This means that now an auto-started service
|
|||
|
can quietly stop itself without reporting an exit code, and we will not
|
|||
|
log an event or put up a popup.
|
|||
|
However, we will still put up a popup if a service stops itself during
|
|||
|
auto-start, and it provides an exit code.
|
|||
|
|
|||
|
20-Oct-1993 danl
|
|||
|
RSetServiceStatus: Only update the status if the service process is
|
|||
|
still running. It is possible that the status could have been blocked
|
|||
|
when the process unexpectedly terminated, and updated the status to
|
|||
|
stopped. In this case, the status that was blocked contains
|
|||
|
out-of-date information.
|
|||
|
|
|||
|
10-Dec-1992 danl
|
|||
|
RI_ScSetServiceBitsW & ScRemoveServiceBits no longer hold locks when
|
|||
|
calling ScNetServerSetServiceBits.
|
|||
|
|
|||
|
03-Nov-1992 danl
|
|||
|
RSetServiceStatus: Remove code that sets ExitCode to ERROR_GEN_FAILURE
|
|||
|
when a service transitions directly from START_PENDING to STOPPED with
|
|||
|
out an exit code of its own.
|
|||
|
|
|||
|
25-Aug-1992 danl
|
|||
|
RSetServiceStatus: Allow dirty checkpoint and exitcode fields.
|
|||
|
Force them clean.
|
|||
|
|
|||
|
19-Jun-1991 danl
|
|||
|
Allow ExitCodes to be specified for the SERVICE_STOP_PENDING state.
|
|||
|
Prior to this they were only allowed for the SERVICE_STOP state.
|
|||
|
|
|||
|
20-Mar-1991 danl
|
|||
|
created
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
//
|
|||
|
// INCLUDES
|
|||
|
//
|
|||
|
|
|||
|
#include "precomp.hxx"
|
|||
|
#include <tstr.h> // Unicode string macros
|
|||
|
|
|||
|
#include "valid.h" // ScCurrentStateInvalid
|
|||
|
#include "depend.h" // ScNotifyChangeState
|
|||
|
#include "driver.h" // ScNotifyNdis
|
|||
|
|
|||
|
#include <lmcons.h> // NET_API_STATUS
|
|||
|
#include <lmerr.h> // NERR_Success
|
|||
|
#include <lmsname.h> // contains service name
|
|||
|
#include <lmserver.h> // SV_TYPE_NT (server announcement bits)
|
|||
|
#include <srvann.h> // I_NetServerSetServiceBits
|
|||
|
|
|||
|
#include "control.h" // SERVICE_SET_STATUS
|
|||
|
|
|||
|
extern "C" {
|
|||
|
|
|||
|
#include <cfgmgr32.h>
|
|||
|
#include "cfgmgrp.h"
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// GLOBALS
|
|||
|
//
|
|||
|
//
|
|||
|
// This is a special storage place for the OR'd server announcement
|
|||
|
// bit masks. NOTE: This is only read or written to when the
|
|||
|
// service database exclusive lock is held.
|
|||
|
//
|
|||
|
DWORD GlobalServerAnnounce = SV_TYPE_NT;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// The following ServerHandle is the handle returned from the
|
|||
|
// LoadLibrary call which loaded netapi.dll. The entrypoint for
|
|||
|
// I_NetServerSetServiceBits is then found and stored in the
|
|||
|
// global location described below.
|
|||
|
//
|
|||
|
HMODULE ScGlobalServerHandle;
|
|||
|
|
|||
|
extern "C" typedef NET_API_STATUS (*SETSBPROC) (
|
|||
|
IN LPTSTR servername,
|
|||
|
IN LPTSTR transport OPTIONAL,
|
|||
|
IN DWORD servicebits,
|
|||
|
IN DWORD updateimmediately
|
|||
|
);
|
|||
|
|
|||
|
SETSBPROC ScNetServerSetServiceBits = NULL;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Function Prototypes (local functions)
|
|||
|
//
|
|||
|
|
|||
|
DWORD
|
|||
|
RemovalThread(
|
|||
|
IN LPSERVICE_RECORD ServiceRecord
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
RSetServiceStatus(
|
|||
|
IN SC_RPC_HANDLE hService,
|
|||
|
IN LPSERVICE_STATUS lpServiceStatus
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function is called by services when they need to inform the
|
|||
|
service controller of a change in state.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
hService - A service handle that has been given to the service
|
|||
|
with SERVICE_SET_STATUS access granted.
|
|||
|
|
|||
|
lpServiceStatus - A pointer to a SERVICE_STATUS structure. This
|
|||
|
reflects the latest status of the calling service.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
ERROR_INVALID_HANDLE - The specified handle is invalid.
|
|||
|
|
|||
|
ERROR_INVALID_SERVICE_STATUS - The specified service status is invalid.
|
|||
|
|
|||
|
Note:
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
DWORD status = NO_ERROR;
|
|||
|
LPSERVICE_RECORD serviceRecord;
|
|||
|
LPSERVICE_RECORD hServiceStatus;
|
|||
|
DWORD threadId;
|
|||
|
HANDLE threadHandle;
|
|||
|
DWORD oldState;
|
|||
|
DWORD oldType;
|
|||
|
BOOL groupListLocked = FALSE;
|
|||
|
|
|||
|
|
|||
|
SC_LOG(TRACE,"In RSetServiceStatus routine\n",0);
|
|||
|
|
|||
|
//
|
|||
|
// Check the handle.
|
|||
|
//
|
|||
|
|
|||
|
if (!ScIsValidServiceHandle(hService))
|
|||
|
{
|
|||
|
return ERROR_INVALID_HANDLE;
|
|||
|
}
|
|||
|
|
|||
|
if (((LPSC_HANDLE_STRUCT)hService)->AccessGranted != SERVICE_SET_STATUS)
|
|||
|
{
|
|||
|
return(ERROR_INVALID_HANDLE);
|
|||
|
}
|
|||
|
|
|||
|
hServiceStatus = ((LPSC_HANDLE_STRUCT) hService)->Type.ScServiceObject.ServiceRecord;
|
|||
|
|
|||
|
//
|
|||
|
// Validate the fields in the service status structure.
|
|||
|
//
|
|||
|
|
|||
|
if (ScCurrentStateInvalid(lpServiceStatus->dwCurrentState))
|
|||
|
{
|
|||
|
SC_LOG2(ERROR, "RSetServiceStatus: " FORMAT_LPWSTR " set invalid "
|
|||
|
" dwCurrentState x%08lx\n",
|
|||
|
((LPSERVICE_RECORD) hServiceStatus)->DisplayName,
|
|||
|
lpServiceStatus->dwCurrentState);
|
|||
|
|
|||
|
ScLogEvent(
|
|||
|
NEVENT_BAD_SERVICE_STATE,
|
|||
|
((LPSERVICE_RECORD) hServiceStatus)->DisplayName,
|
|||
|
lpServiceStatus->dwCurrentState
|
|||
|
);
|
|||
|
|
|||
|
return(ERROR_INVALID_DATA);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if( (SERVICE_STATUS_TYPE_INVALID(lpServiceStatus->dwServiceType)) ||
|
|||
|
(CONTROLS_ACCEPTED_INVALID(lpServiceStatus->dwControlsAccepted)) )
|
|||
|
{
|
|||
|
SC_LOG3(ERROR,
|
|||
|
"RSetServiceStatus: Error in one of the following for service %ws\n"
|
|||
|
"\tServiceType %#x\n"
|
|||
|
"\tControlsAccepted %#x\n",
|
|||
|
((LPSERVICE_RECORD) hServiceStatus)->DisplayName,
|
|||
|
lpServiceStatus->dwServiceType,
|
|||
|
lpServiceStatus->dwControlsAccepted);
|
|||
|
|
|||
|
return(ERROR_INVALID_DATA);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the service is not in the stopped or stop-pending state, then the
|
|||
|
// exit code fields should be 0.
|
|||
|
//
|
|||
|
if (((lpServiceStatus->dwCurrentState != SERVICE_STOPPED) &&
|
|||
|
(lpServiceStatus->dwCurrentState != SERVICE_STOP_PENDING))
|
|||
|
|
|||
|
&&
|
|||
|
|
|||
|
((lpServiceStatus->dwWin32ExitCode != 0) ||
|
|||
|
(lpServiceStatus->dwServiceSpecificExitCode != 0)) )
|
|||
|
{
|
|||
|
SC_LOG(TRACE,"RSetServiceStatus: ExitCode fields not cleaned up "
|
|||
|
"when state indicates SERVICE_STOPPED\n",0);
|
|||
|
|
|||
|
lpServiceStatus->dwWin32ExitCode = 0;
|
|||
|
lpServiceStatus->dwServiceSpecificExitCode = 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the service is not in a pending state, then the waitHint and
|
|||
|
// checkPoint fields should be 0.
|
|||
|
//
|
|||
|
if ( ( (lpServiceStatus->dwCurrentState == SERVICE_STOPPED) ||
|
|||
|
(lpServiceStatus->dwCurrentState == SERVICE_RUNNING) ||
|
|||
|
(lpServiceStatus->dwCurrentState == SERVICE_PAUSED) )
|
|||
|
&&
|
|||
|
( (lpServiceStatus->dwCheckPoint != 0) ||
|
|||
|
(lpServiceStatus->dwWaitHint != 0) ) )
|
|||
|
{
|
|||
|
SC_LOG(TRACE,"RSetServiceStatus: Dirty Checkpoint and WaitHint fields\n",0);
|
|||
|
lpServiceStatus->dwCheckPoint = 0;
|
|||
|
lpServiceStatus->dwWaitHint = 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// If the service has stopped, ScRemoveServiceBits needs the service
|
|||
|
// list lock with shared access.
|
|||
|
//
|
|||
|
if (lpServiceStatus->dwCurrentState == SERVICE_STOPPED)
|
|||
|
{
|
|||
|
ScServiceListLock.GetShared();
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Update the service record. Exclusive locks are required for this.
|
|||
|
//
|
|||
|
// NOTICE that we don't destroy the ServiceType information that was
|
|||
|
// in the service record.
|
|||
|
//
|
|||
|
serviceRecord = (LPSERVICE_RECORD)hServiceStatus;
|
|||
|
|
|||
|
SC_LOG(TRACE,"RSetServiceStatus: Status field accepted, service %ws\n",
|
|||
|
serviceRecord->ServiceName);
|
|||
|
|
|||
|
ScServiceRecordLock.GetExclusive();
|
|||
|
|
|||
|
//
|
|||
|
// If the service stopped, and its update flag is set (its config was
|
|||
|
// changed while it was running) we may need the group list lock in
|
|||
|
// ScRemoveService. So release the locks and reacquire them after
|
|||
|
// getting the group list lock. This is a rare occurrence, hence not
|
|||
|
// optimized.
|
|||
|
//
|
|||
|
if (lpServiceStatus->dwCurrentState == SERVICE_STOPPED &&
|
|||
|
UPDATE_FLAG_IS_SET(serviceRecord))
|
|||
|
{
|
|||
|
ScServiceRecordLock.Release();
|
|||
|
ScServiceListLock.Release();
|
|||
|
|
|||
|
ScGroupListLock.GetExclusive();
|
|||
|
ScServiceListLock.GetShared();
|
|||
|
ScServiceRecordLock.GetExclusive();
|
|||
|
groupListLocked = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
oldState = serviceRecord->ServiceStatus.dwCurrentState;
|
|||
|
oldType = serviceRecord->ServiceStatus.dwServiceType;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// It is possible that while we were blocked waiting for the lock,
|
|||
|
// that a running service could have terminated (Due to the process
|
|||
|
// terminating). So we need to look for "late" status updates, and
|
|||
|
// filter them out. If the ImageRecord pointer is NULL, then the
|
|||
|
// Service has Terminated. Otherwise update the status.
|
|||
|
//
|
|||
|
if (serviceRecord->ImageRecord != NULL)
|
|||
|
{
|
|||
|
//
|
|||
|
// Don't bother notifying PnP if the system is shutting down. This
|
|||
|
// prevents a deadlock where we can get stuck calling PnP, which is
|
|||
|
// stuck calling into the Eventlog, which is stuck calling into us.
|
|||
|
//
|
|||
|
if (!ScShutdownInProgress)
|
|||
|
{
|
|||
|
DWORD dwControlFlags;
|
|||
|
DWORD dwBitMask;
|
|||
|
|
|||
|
dwControlFlags = serviceRecord->ServiceStatus.dwControlsAccepted
|
|||
|
&
|
|||
|
(SERVICE_ACCEPT_HARDWAREPROFILECHANGE |
|
|||
|
SERVICE_ACCEPT_POWEREVENT);
|
|||
|
|
|||
|
dwBitMask = lpServiceStatus->dwControlsAccepted ^ dwControlFlags;
|
|||
|
|
|||
|
if (dwBitMask
|
|||
|
||
|
|||
|
((lpServiceStatus->dwCurrentState == SERVICE_STOPPED) && dwControlFlags))
|
|||
|
{
|
|||
|
DWORD dwRetVal;
|
|||
|
|
|||
|
//
|
|||
|
// The service is either changing its registration status for
|
|||
|
// hardware profile change notifications or power OR is stopping,
|
|||
|
// so inform PnP of this. On service stop, deregister the service
|
|||
|
// if it accepts power or hardware profile change notifications.
|
|||
|
//
|
|||
|
dwRetVal = RegisterServiceNotification(
|
|||
|
(SERVICE_STATUS_HANDLE)hServiceStatus,
|
|||
|
serviceRecord->ServiceName,
|
|||
|
lpServiceStatus->dwCurrentState != SERVICE_STOPPED ?
|
|||
|
lpServiceStatus->dwControlsAccepted : 0,
|
|||
|
(lpServiceStatus->dwCurrentState == SERVICE_STOPPED));
|
|||
|
|
|||
|
if (dwRetVal != CR_SUCCESS)
|
|||
|
{
|
|||
|
SC_LOG3(ERROR,
|
|||
|
"Hardware profile change and power %sregistration failed "
|
|||
|
"for service %ws with config manager error %d\n",
|
|||
|
(lpServiceStatus->dwControlsAccepted &
|
|||
|
(SERVICE_ACCEPT_HARDWAREPROFILECHANGE |
|
|||
|
SERVICE_ACCEPT_POWEREVENT)) ?
|
|||
|
"" :
|
|||
|
"de",
|
|||
|
serviceRecord->ServiceName,
|
|||
|
dwRetVal);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Update to the new status
|
|||
|
//
|
|||
|
RtlCopyMemory(&(serviceRecord->ServiceStatus),
|
|||
|
lpServiceStatus,
|
|||
|
sizeof(SERVICE_STATUS));
|
|||
|
|
|||
|
serviceRecord->ServiceStatus.dwServiceType = oldType;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// For dependency handling
|
|||
|
//
|
|||
|
if ((serviceRecord->ServiceStatus.dwCurrentState == SERVICE_RUNNING ||
|
|||
|
serviceRecord->ServiceStatus.dwCurrentState == SERVICE_STOPPED ||
|
|||
|
serviceRecord->ServiceStatus.dwCurrentState == SERVICE_STOP_PENDING) &&
|
|||
|
oldState == SERVICE_START_PENDING)
|
|||
|
{
|
|||
|
if (serviceRecord->ServiceStatus.dwCurrentState == SERVICE_STOPPED ||
|
|||
|
serviceRecord->ServiceStatus.dwCurrentState == SERVICE_STOP_PENDING)
|
|||
|
{
|
|||
|
serviceRecord->StartState = SC_START_FAIL;
|
|||
|
SC_LOG(DEPEND, "%ws START_PENDING -> FAIL\n", serviceRecord->ServiceName);
|
|||
|
}
|
|||
|
else if (serviceRecord->ServiceStatus.dwCurrentState == SERVICE_RUNNING)
|
|||
|
{
|
|||
|
serviceRecord->StartState = SC_START_SUCCESS;
|
|||
|
SC_LOG(DEPEND, "%ws START_PENDING -> RUNNING\n", serviceRecord->ServiceName);
|
|||
|
#ifdef TIMING_TEST
|
|||
|
DbgPrint("[SC_TIMING] TickCount for RUNNING service \t%ws\t%d\n",
|
|||
|
serviceRecord->ServiceName, GetTickCount());
|
|||
|
#endif // TIMING_TEST
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Tell the dependency handling code that a start-pending
|
|||
|
// service is now running or stopped.
|
|||
|
//
|
|||
|
ScNotifyChangeState();
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Note: We no longer need an exclusive lock on the service records,
|
|||
|
// but we still need at least a shared lock, since we read the
|
|||
|
// DisplayName and ErrorControl fields below. (If we didn't have a
|
|||
|
// shared lock, ChangeServiceConfig could change the fields under
|
|||
|
// us.) Later, we call ScRemoveServiceBits and ScRemoveService,
|
|||
|
// which acquire an exclusive lock, which is problematic if we
|
|||
|
// already have a shared lock.
|
|||
|
// To keep things simple, we just hold onto the exclusive lock.
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// Log an event about the service's new state if appropriate. Don't
|
|||
|
// do this during auto-start to avoid hurting boot performance and
|
|||
|
// filling the log with a start event for every auto-start service.
|
|||
|
//
|
|||
|
if (!ScAutoStartInProgress
|
|||
|
&&
|
|||
|
lpServiceStatus->dwCurrentState != oldState
|
|||
|
&&
|
|||
|
IS_STATUS_LOGGABLE(lpServiceStatus->dwCurrentState))
|
|||
|
{
|
|||
|
ScLogControlEvent(NEVENT_SERVICE_STATUS_SUCCESS,
|
|||
|
serviceRecord->DisplayName,
|
|||
|
lpServiceStatus->dwCurrentState);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// If the new status indicates that the service has just started,
|
|||
|
// tell NDIS to issue the PNP notifications about this service's arrival,
|
|||
|
// if it belongs to one of the groups NDIS is interested in.
|
|||
|
//
|
|||
|
if ((lpServiceStatus->dwCurrentState == SERVICE_RUNNING) &&
|
|||
|
(oldState != SERVICE_RUNNING))
|
|||
|
{
|
|||
|
ScNotifyNdis(serviceRecord);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the new status indicates that the service has just stopped,
|
|||
|
// we need to check to see if there are any other services running
|
|||
|
// in the service process. If not, then we can ask the service to
|
|||
|
// terminate. Another thread is spawned to handle this since we need
|
|||
|
// to return from this call in order to allow the service to complete
|
|||
|
// its shutdown.
|
|||
|
//
|
|||
|
|
|||
|
else if ((lpServiceStatus->dwCurrentState == SERVICE_STOPPED) &&
|
|||
|
(oldState != SERVICE_STOPPED))
|
|||
|
{
|
|||
|
if (lpServiceStatus->dwWin32ExitCode != NO_ERROR)
|
|||
|
{
|
|||
|
if (lpServiceStatus->dwWin32ExitCode != ERROR_SERVICE_SPECIFIC_ERROR)
|
|||
|
{
|
|||
|
ScLogEvent(
|
|||
|
NEVENT_SERVICE_EXIT_FAILED,
|
|||
|
serviceRecord->DisplayName,
|
|||
|
lpServiceStatus->dwWin32ExitCode
|
|||
|
);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
ScLogEvent(
|
|||
|
NEVENT_SERVICE_EXIT_FAILED_SPECIFIC,
|
|||
|
serviceRecord->DisplayName,
|
|||
|
lpServiceStatus->dwServiceSpecificExitCode
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// For popup after user has logged on to indicate that some service
|
|||
|
// started at boot has failed.
|
|||
|
//
|
|||
|
if (serviceRecord->ErrorControl == SERVICE_ERROR_NORMAL ||
|
|||
|
serviceRecord->ErrorControl == SERVICE_ERROR_SEVERE ||
|
|||
|
serviceRecord->ErrorControl == SERVICE_ERROR_CRITICAL)
|
|||
|
{
|
|||
|
ScPopupStartFail = TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Clear the server announcement bits in the global location
|
|||
|
// for this service.
|
|||
|
//
|
|||
|
ScRemoveServiceBits(serviceRecord);
|
|||
|
|
|||
|
//
|
|||
|
// If this is the last service in the process, then delete the
|
|||
|
// process handle from the ProcessWatcher list.
|
|||
|
//
|
|||
|
if ((serviceRecord->ImageRecord != NULL) &&
|
|||
|
(serviceRecord->ImageRecord->ServiceCount == 1))
|
|||
|
{
|
|||
|
NTSTATUS ntStatus;
|
|||
|
|
|||
|
//
|
|||
|
// Check vs. NULL in case the work item registration failed.
|
|||
|
// Deregister here so the process cleanup routine doesn't get
|
|||
|
// called if the service process exits between now and when we
|
|||
|
// call ScRemoveService.
|
|||
|
//
|
|||
|
if (serviceRecord->ImageRecord->ObjectWaitHandle != NULL) {
|
|||
|
|
|||
|
ntStatus = RtlDeregisterWait(serviceRecord->ImageRecord->ObjectWaitHandle);
|
|||
|
|
|||
|
if (NT_SUCCESS(ntStatus)) {
|
|||
|
serviceRecord->ImageRecord->ObjectWaitHandle = NULL;
|
|||
|
}
|
|||
|
else {
|
|||
|
|
|||
|
SC_LOG1(ERROR,
|
|||
|
"RSetServiceStatus: RtlDeregisterWait failed 0x%x\n",
|
|||
|
ntStatus);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Even though the service said it stopped, save a status of
|
|||
|
// STOP_PENDING. ScRemoveService will set it to STOPPED. This
|
|||
|
// is to prevent anyone else from trying to restart the service
|
|||
|
// between the time that our thread releases the locks here and
|
|||
|
// the time that ScRemoveService (in the thread we are about to
|
|||
|
// create) acquires the locks. ScRemoveService must get to the
|
|||
|
// service first, because it may need to process an UPDATE_FLAG
|
|||
|
// set on the service before it's OK to restart it.
|
|||
|
//
|
|||
|
serviceRecord->ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
|
|||
|
|
|||
|
SC_LOG(TRACE,
|
|||
|
"RSetServiceStatus:Create a thread to run ScRemoveService\n",0);
|
|||
|
|
|||
|
threadHandle = CreateThread (
|
|||
|
NULL, // Thread Attributes.
|
|||
|
0L, // Stack Size
|
|||
|
(LPTHREAD_START_ROUTINE)RemovalThread, // lpStartAddress
|
|||
|
(LPVOID)serviceRecord, // lpParameter
|
|||
|
0L, // Creation Flags
|
|||
|
&threadId); // lpThreadId
|
|||
|
|
|||
|
if (threadHandle == (HANDLE) NULL)
|
|||
|
{
|
|||
|
SC_LOG(ERROR,"RSetServiceStatus:CreateThread failed %d\n",
|
|||
|
GetLastError());
|
|||
|
|
|||
|
//
|
|||
|
// If a thread couldn't be created to remove the service, it is removed
|
|||
|
// in the context of this thread. The result of this is a somewhat
|
|||
|
// dirty termination. The service record will be removed from the
|
|||
|
// installed database. If this was the last service in the process,
|
|||
|
// the process will terminate before we return to the thread. Note that
|
|||
|
// we must release the locks before calling ScRemoveService since the
|
|||
|
// first thing it does is to acquire the list lock -- not releasing here
|
|||
|
// will cause deadlock (bug #103102). The GroupListLock is not released
|
|||
|
// since ScRemoveService will acquire it in the same thread -- instead
|
|||
|
// of just releasing and then immediately reacquiring, release afterwards.
|
|||
|
//
|
|||
|
|
|||
|
ScServiceRecordLock.Release();
|
|||
|
ScServiceListLock.Release();
|
|||
|
|
|||
|
SC_LOG0(TRACE,"Attempting an in-thread removal in RSetServiceStatus\n");
|
|||
|
|
|||
|
status = ScRemoveService(serviceRecord);
|
|||
|
|
|||
|
if (groupListLocked)
|
|||
|
{
|
|||
|
ScGroupListLock.Release();
|
|||
|
}
|
|||
|
|
|||
|
return status;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//
|
|||
|
// The Thread Creation was successful.
|
|||
|
//
|
|||
|
SC_LOG(TRACE,"Thread Creation Success, thread id = %#lx\n",threadId);
|
|||
|
CloseHandle(threadHandle);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Release the locks we got earlier
|
|||
|
//
|
|||
|
ScServiceRecordLock.Release();
|
|||
|
|
|||
|
if (lpServiceStatus->dwCurrentState == SERVICE_STOPPED)
|
|||
|
{
|
|||
|
ScServiceListLock.Release();
|
|||
|
if (groupListLocked)
|
|||
|
{
|
|||
|
ScGroupListLock.Release();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
SC_LOG(TRACE,"Return from RSetServiceStatus\n",0);
|
|||
|
|
|||
|
return(status);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
RemovalThread(
|
|||
|
IN LPSERVICE_RECORD ServiceRecord
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This thread is used by RSetServiceStatus to remove a service from the
|
|||
|
Service Controller's database, and also - if necessary - shut down
|
|||
|
the service process. The later step is only done if this was the last
|
|||
|
service running in that process.
|
|||
|
|
|||
|
The use of this thread allows RSetServiceStatus to return to the service
|
|||
|
so that the service can then continue to terminate itself.
|
|||
|
|
|||
|
We know that the service record will not go away before this thread
|
|||
|
acquires an exclusive lock on the database, because the service record's
|
|||
|
use count is not zero.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ServiceRecord - This is a pointer to the service record that is being
|
|||
|
removed.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Same as return values for RemoveService().
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
ScRemoveService (ServiceRecord);
|
|||
|
|
|||
|
return(0);
|
|||
|
}
|
|||
|
|
|||
|
DWORD
|
|||
|
RI_ScSetServiceBitsA(
|
|||
|
IN SC_RPC_HANDLE hService,
|
|||
|
IN DWORD dwServiceBits,
|
|||
|
IN DWORD bSetBitsOn,
|
|||
|
IN DWORD bUpdateImmediately,
|
|||
|
IN LPSTR pszReserved
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function Or's the Service Bits that are passed in - into a
|
|||
|
global bitmask maintained by the service controller. Everytime this
|
|||
|
function is called, we check to see if the server service is running.
|
|||
|
If it is, then we call an internal entry point in the server service
|
|||
|
to pass in the complete bitmask.
|
|||
|
|
|||
|
This function also Or's the Service Bits into the ServerAnnounce
|
|||
|
element in the service's ServiceRecord.
|
|||
|
|
|||
|
NOTE: The exclusive database lock is obtained and held while the
|
|||
|
service record is being read, and while the GlobalServerAnnounce
|
|||
|
bits are set.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
NO_ERROR - The operation was completely successful. The information
|
|||
|
may or may not be delivered to the Server depending on if it is
|
|||
|
running or not.
|
|||
|
|
|||
|
or any error returned from the server service I_NetServerSetServiceBits
|
|||
|
function.
|
|||
|
|
|||
|
Note:
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
if (ScShutdownInProgress) {
|
|||
|
return(ERROR_SHUTDOWN_IN_PROGRESS);
|
|||
|
}
|
|||
|
|
|||
|
if (pszReserved != NULL) {
|
|||
|
return (ERROR_INVALID_PARAMETER);
|
|||
|
}
|
|||
|
|
|||
|
return RI_ScSetServiceBitsW((SC_RPC_HANDLE)hService,
|
|||
|
dwServiceBits,
|
|||
|
bSetBitsOn,
|
|||
|
bUpdateImmediately,
|
|||
|
NULL);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
DWORD
|
|||
|
RI_ScSetServiceBitsW(
|
|||
|
IN SC_RPC_HANDLE hService,
|
|||
|
IN DWORD dwServiceBits,
|
|||
|
IN DWORD bSetBitsOn,
|
|||
|
IN DWORD bUpdateImmediately,
|
|||
|
IN LPWSTR pszReserved
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function Or's the Service Bits that are passed in - into a
|
|||
|
global bitmask maintained by the service controller. Everytime this
|
|||
|
function is called, we check to see if the server service is running.
|
|||
|
If it is, then we call an internal entry point in the server service
|
|||
|
to pass in the complete bitmask.
|
|||
|
|
|||
|
This function also Or's the Service Bits into the ServerAnnounce
|
|||
|
element in the service's ServiceRecord.
|
|||
|
|
|||
|
NOTE: The exclusive database lock is obtained and held while the
|
|||
|
service record is being read, and while the GlobalServerAnnounce
|
|||
|
bits are set.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
NO_ERROR - The operation was completely successful.
|
|||
|
|
|||
|
ERROR_GEN_FAILURE - The server service is there, but the call to
|
|||
|
update it failed.
|
|||
|
|
|||
|
Note:
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
DWORD status = NO_ERROR;
|
|||
|
LPSERVICE_RECORD serviceRecord;
|
|||
|
LPWSTR serverServiceName;
|
|||
|
DWORD serviceState;
|
|||
|
|
|||
|
if (ScShutdownInProgress) {
|
|||
|
return(ERROR_SHUTDOWN_IN_PROGRESS);
|
|||
|
}
|
|||
|
|
|||
|
if (pszReserved != NULL) {
|
|||
|
return (ERROR_INVALID_PARAMETER);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Check the handle.
|
|||
|
//
|
|||
|
if (!ScIsValidServiceHandle(hService))
|
|||
|
{
|
|||
|
return ERROR_INVALID_HANDLE;
|
|||
|
}
|
|||
|
|
|||
|
if (((LPSC_HANDLE_STRUCT)hService)->AccessGranted != SERVICE_SET_STATUS) {
|
|||
|
return(ERROR_INVALID_HANDLE);
|
|||
|
}
|
|||
|
|
|||
|
if (ScNetServerSetServiceBits == (SETSBPROC)NULL) {
|
|||
|
if (! ScInitServerAnnounceFcn()) {
|
|||
|
return(ERROR_NO_NETWORK);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
serverServiceName = SERVICE_SERVER;
|
|||
|
|
|||
|
CServiceListSharedLock LLock;
|
|||
|
CServiceRecordExclusiveLock RLock;
|
|||
|
|
|||
|
serviceRecord = ((LPSC_HANDLE_STRUCT) hService)->Type.ScServiceObject.ServiceRecord;
|
|||
|
|
|||
|
if (bSetBitsOn) {
|
|||
|
//
|
|||
|
// Set the bits in the global location.
|
|||
|
//
|
|||
|
GlobalServerAnnounce |= dwServiceBits;
|
|||
|
|
|||
|
//
|
|||
|
// Set the bits in the service record.
|
|||
|
//
|
|||
|
|
|||
|
serviceRecord->ServerAnnounce |= dwServiceBits;
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
else {
|
|||
|
//
|
|||
|
// Clear the bits in the global location.
|
|||
|
//
|
|||
|
GlobalServerAnnounce &= ~dwServiceBits;
|
|||
|
|
|||
|
//
|
|||
|
// Clear the bits in the service record.
|
|||
|
//
|
|||
|
|
|||
|
serviceRecord->ServerAnnounce &= ~dwServiceBits;
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
//
|
|||
|
// If the server service is running, then send the Global mask to
|
|||
|
// the server service.
|
|||
|
//
|
|||
|
|
|||
|
status = ScGetNamedServiceRecord(
|
|||
|
serverServiceName,
|
|||
|
&serviceRecord);
|
|||
|
|
|||
|
if (status == NO_ERROR) {
|
|||
|
|
|||
|
serviceState = serviceRecord->ServiceStatus.dwCurrentState;
|
|||
|
|
|||
|
if (serviceState == SERVICE_RUNNING) {
|
|||
|
|
|||
|
status = ScNetServerSetServiceBits(
|
|||
|
NULL, // ServerName
|
|||
|
NULL, // TransportName
|
|||
|
GlobalServerAnnounce,
|
|||
|
bUpdateImmediately);
|
|||
|
|
|||
|
if (status != NERR_Success) {
|
|||
|
SC_LOG(ERROR,"I_ScSetServiceBits: I_NetServerSetServiceBits failed %lu\n",
|
|||
|
status);
|
|||
|
}
|
|||
|
else {
|
|||
|
SC_LOG(TRACE,"I_ScSetServiceBits: I_NetServerSetServiceBits success\n",0);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else {
|
|||
|
status = NO_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
SC_LOG(TRACE,"I_ScSetServiceBits: GlobalServerAnnounce = 0x%lx\n",
|
|||
|
GlobalServerAnnounce);
|
|||
|
|
|||
|
return(status);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
ScRemoveServiceBits(
|
|||
|
IN LPSERVICE_RECORD ServiceRecord
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function is called when a service stops running. It looks in
|
|||
|
the service record for any server announcement bits that are set
|
|||
|
and turns them off in GlobalServerAnnounce. The ServerAnnounce
|
|||
|
element in the service record is set to 0.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ServiceRecord - This is a pointer to the service record that
|
|||
|
has changed to the stopped state.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
The status returned from I_NetServerSetServiceBits.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
DWORD status = NO_ERROR;
|
|||
|
LPSERVICE_RECORD serverServiceRecord;
|
|||
|
DWORD serviceState;
|
|||
|
|
|||
|
|
|||
|
if (ScNetServerSetServiceBits == (SETSBPROC)NULL) {
|
|||
|
if (! ScInitServerAnnounceFcn()) {
|
|||
|
return(ERROR_NO_NETWORK);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (ServiceRecord->ServerAnnounce != 0) {
|
|||
|
|
|||
|
CServiceRecordExclusiveLock RLock;
|
|||
|
|
|||
|
//
|
|||
|
// Clear the bits in the global location.
|
|||
|
//
|
|||
|
GlobalServerAnnounce &= ~(ServiceRecord->ServerAnnounce);
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Clear the bits in the service record.
|
|||
|
//
|
|||
|
|
|||
|
ServiceRecord->ServerAnnounce = 0;
|
|||
|
|
|||
|
SC_LOG1(TRACE,"RemoveServiceBits: New GlobalServerAnnounce = 0x%lx\n",
|
|||
|
GlobalServerAnnounce);
|
|||
|
|
|||
|
//
|
|||
|
// If the server service is running, then send the Global mask to
|
|||
|
// the server service.
|
|||
|
//
|
|||
|
|
|||
|
status = ScGetNamedServiceRecord(
|
|||
|
SERVICE_SERVER,
|
|||
|
&serverServiceRecord);
|
|||
|
|
|||
|
|
|||
|
if (status == NO_ERROR) {
|
|||
|
|
|||
|
serviceState = serverServiceRecord->ServiceStatus.dwCurrentState;
|
|||
|
|
|||
|
if ( serviceState == SERVICE_RUNNING) {
|
|||
|
|
|||
|
status = ScNetServerSetServiceBits(
|
|||
|
NULL, // ServerName
|
|||
|
NULL, // Transport name
|
|||
|
GlobalServerAnnounce,
|
|||
|
TRUE); // Update immediately.
|
|||
|
|
|||
|
if (status != NERR_Success) {
|
|||
|
SC_LOG(ERROR,"ScRemoveServiceBits: I_NetServerSetServiceBits failed %d\n",
|
|||
|
status);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return(status);
|
|||
|
}
|
|||
|
|
|||
|
BOOL
|
|||
|
ScInitServerAnnounceFcn(
|
|||
|
VOID
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
ScGlobalServerHandle = LoadLibraryW(L"netapi32.dll");
|
|||
|
|
|||
|
if (ScGlobalServerHandle == NULL) {
|
|||
|
SC_LOG(ERROR,"ScInitServerAnnouncFcn: LoadLibrary failed %d\n",
|
|||
|
GetLastError());
|
|||
|
return(FALSE);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Use I_NetServerSetServiceBits rather than the Ex version since
|
|||
|
// it uses a special flag to prevent the RPC failure path from
|
|||
|
// calling back into services.exe and potentially causing a deadlock.
|
|||
|
//
|
|||
|
|
|||
|
ScNetServerSetServiceBits = (SETSBPROC)GetProcAddress(
|
|||
|
ScGlobalServerHandle,
|
|||
|
"I_NetServerSetServiceBits");
|
|||
|
|
|||
|
|
|||
|
if (ScNetServerSetServiceBits == (SETSBPROC)NULL) {
|
|||
|
SC_LOG(ERROR,"ScInitServerAnnouncFcn: GetProcAddress failed %d\n",
|
|||
|
GetLastError());
|
|||
|
return(FALSE);
|
|||
|
}
|
|||
|
return TRUE;
|
|||
|
}
|