Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

888 lines
25 KiB

/*++
Copyright (c) 1991 Microsoft Corporation
Module Name:
status.c
Abstract:
This file contains functions that are involved with setting the
status for a service in the service controller.
RSetServiceStatus
Removal Thread
RI_ScSetServiceBitsA
RI_ScSetServiceBitsW
ScRemoveServiceBits
ScInitServerAnnounceFcn
Author:
Dan Lafferty (danl) 20-Mar-1991
Environment:
User Mode -Win32
Revision History:
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 <nt.h>
#include <ntrtl.h> // DbgPrint prototype
#include <rpc.h> // DataTypes and runtime APIs
#include <nturtl.h> // needed for winbase.h
#include <windows.h> // WaitForSingleObject
#include <svcctl.h> // MIDL Generated Header File
#include <tstr.h> // Unicode string macros
#include <winsvc.h> // public Service Controller Interface.
#include <scdebug.h> // SC_LOG
#include "dataman.h" // LPIMAGE_RECORD
#include "scopen.h" // Handle structures and signature definitions
#include "valid.h" // ScCurrentStateInvalid
#include "svcctrl.h" // ScRemoveServiceBits
#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 <svcslib.h> // SvcRemoveWorkItem
//
// 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.
//
HANDLE ScGlobalServerHandle;
typedef DWORD (WINAPI *SETSBPROC)();
SETSBPROC ScNetServerSetServiceBits = NULL;
//
// Function Prototypes (local functions)
//
DWORD
RemovalThread(
IN LPSERVICE_RECORD ServiceRecord
);
DWORD
RSetServiceStatus(
IN SERVICE_STATUS_HANDLE hServiceStatus,
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:
hServiceStatus - A DWORD value that is actually a pointer to a
service record in the database. Since this is not a
handle of SC_HANDLE type, there is no signature to check for
this handle.
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;
DWORD threadId;
HANDLE threadHandle;
DWORD oldState;
DWORD oldType;
LPWSTR ScSubStrings[2];
WCHAR ScErrorCodeString[25];
SC_LOG(TRACE,"In RSetServiceStatus routine\n",0);
if (hServiceStatus == (SERVICE_STATUS_HANDLE) NULL) {
return(ERROR_INVALID_HANDLE);
}
try {
//
// Check the signature on the handle.
//
if (((LPSERVICE_RECORD)hServiceStatus)->Signature != SERVICE_SIGNATURE) {
SC_LOG(TRACE,"RSetServiceStatus: hServiceStatus was invalid\n",0);
status = ERROR_INVALID_HANDLE;
}
}
except(EXCEPTION_EXECUTE_HANDLER) {
status = ERROR_INVALID_HANDLE;
}
if (status != NO_ERROR) {
return(status);
}
//
// 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);
ScSubStrings[0] = ((LPSERVICE_RECORD) hServiceStatus)->DisplayName;
ScSubStrings[1] = ultow(
lpServiceStatus->dwCurrentState,
ScErrorCodeString,
16
);
ScLogEvent(
EVENT_BAD_SERVICE_STATE,
2,
ScSubStrings
);
return(ERROR_INVALID_DATA);
}
if( (SERVICE_STATUS_TYPE_INVALID(lpServiceStatus->dwServiceType)) ||
(CONTROLS_ACCEPTED_INVALID(lpServiceStatus->dwControlsAccepted)) ) {
SC_LOG(ERROR,"RSetServiceStatus: Error in one of the following\n"
" ServiceType x%08lx\n",
lpServiceStatus->dwServiceType);
SC_LOG(ERROR," ControlsAccepted x%08lx\n",
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;
}
//
// 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);
ScDatabaseLock( SC_GET_EXCLUSIVE,"RSetServiceStatus1");
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) {
//
// Update to the new status
//
memcpy(
&(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();
}
//
// 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)) {
ScDatabaseLock(SC_MAKE_SHARED,"RSetServiceStatus1.5");
ScNotifyNdis(serviceRecord);
}
ScDatabaseLock(SC_RELEASE,"RSetServiceStatus2");
//
// 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.
//
if ((lpServiceStatus->dwCurrentState == SERVICE_STOPPED) &&
(oldState != SERVICE_STOPPED)) {
if (lpServiceStatus->dwWin32ExitCode != NO_ERROR) {
if (lpServiceStatus->dwWin32ExitCode !=
ERROR_SERVICE_SPECIFIC_ERROR) {
ScSubStrings[0] = serviceRecord->DisplayName;
wcscpy(ScErrorCodeString,L"%%");
ultow(lpServiceStatus->dwWin32ExitCode, ScErrorCodeString+2, 10);
ScSubStrings[1] = ScErrorCodeString;
ScLogEvent(
EVENT_SERVICE_EXIT_FAILED,
2,
ScSubStrings
);
}
else {
ScSubStrings[0] = serviceRecord->DisplayName;
ScSubStrings[1] = ultow(
lpServiceStatus->dwServiceSpecificExitCode,
ScErrorCodeString,
10
);
ScLogEvent(
EVENT_SERVICE_EXIT_FAILED_SPECIFIC,
2,
ScSubStrings
);
}
//
// 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);
//
// BUGBUG: If possible, don't do anything with locks here.
// It would be better if ScRemoveService would get
// the exclusive lock itself - rather than expect the
// shared lock and converting it to exclusive.
// Check into places that call ScRemoveService.
//
ScDatabaseLock( SC_GET_SHARED,"RSetServiceStatus3");
//
// 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)) {
if (SvcRemoveWorkItem(serviceRecord->ImageRecord->ObjectWaitHandle)) {
serviceRecord->ImageRecord->ObjectWaitHandle = NULL;
}
}
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.
//
status = ScRemoveService(serviceRecord);
ScDatabaseLock( SC_RELEASE,"RSetServiceStatus4");
}
else {
//
// The Thread Creation was successful. Allow that thread
// to free the lock.
//
SC_LOG(TRACE,"Thread Creation Success, thread id = %#lx\n",threadId);
CloseHandle(threadHandle);
}
}
SC_LOG(TRACE,"Return from RSetServiceStatus\n",0);
return(status);
}
DWORD
RemovalThread(
IN LPSERVICE_RECORD ServiceRecord
)
/*++
Routine Description:
This thread is used by NetrServiceStatus 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 NetServiceStatus to return to the service
so that the service can then continue to terminate itself.
QUESTION:
Am I going to have synchronization problems with the database
by passing in a pointer to the ServiceRecord? How should I work
locks on the database?
Arguments:
ServiceRecord - This is a pointer to the service record that is being
removed.
Return Value:
Same as return values for RemoveService().
Note:
--*/
{
//
// RemoveService assumes that the shared lock is already obtained.
//
ScRemoveService (ServiceRecord);
ScDatabaseLock( SC_RELEASE, "RemovalThread1");
ExitThread(0);
return(0);
}
DWORD
RI_ScSetServiceBitsA(
IN SERVICE_STATUS_HANDLE hServiceStatus,
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:
--*/
{
DWORD status = NO_ERROR;
if (ScShutdownInProgress) {
return(ERROR_SHUTDOWN_IN_PROGRESS);
}
if (pszReserved != NULL) {
return (ERROR_INVALID_PARAMETER);
}
return RI_ScSetServiceBitsW(hServiceStatus,
dwServiceBits,
bSetBitsOn,
bUpdateImmediately,
NULL);
}
DWORD
RI_ScSetServiceBitsW(
IN SERVICE_STATUS_HANDLE hServiceStatus,
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);
}
if (ScNetServerSetServiceBits == (SETSBPROC)NULL) {
if (! ScInitServerAnnounceFcn()) {
return(ERROR_NO_NETWORK);
}
}
serverServiceName = SERVICE_SERVER;
try {
//
// Check the signature on the handle.
//
if (((LPSERVICE_RECORD)hServiceStatus)->Signature != SERVICE_SIGNATURE) {
SC_LOG(ERROR,"RI_ScSetServiceBitsW: hServiceStatus %#lx was invalid\n",
hServiceStatus);
status = ERROR_INVALID_HANDLE;
}
}
except(EXCEPTION_EXECUTE_HANDLER) {
status = ERROR_INVALID_HANDLE;
}
if (status != NO_ERROR) {
return(status);
}
ScDatabaseLock(SC_GET_EXCLUSIVE,"ScSetServiceBits1");
serviceRecord = (LPSERVICE_RECORD)hServiceStatus;
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;
ScDatabaseLock(SC_RELEASE,"ScSetServiceBits2");
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 {
ScDatabaseLock(SC_RELEASE,"ScSetServiceBits3");
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) {
ScDatabaseLock(SC_GET_EXCLUSIVE,"ScRemoveServiceBits1");
//
// 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;
ScDatabaseLock(SC_RELEASE,"ScRemoveServiceBits2");
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);
}
}
}
else {
ScDatabaseLock(SC_RELEASE,"ScRemoveServiceBits2");
}
}
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);
}
ScNetServerSetServiceBits = (SETSBPROC)GetProcAddress(
ScGlobalServerHandle,
"I_NetServerSetServiceBits");
if (ScNetServerSetServiceBits == (SETSBPROC)NULL) {
SC_LOG(ERROR,"ScInitServerAnnouncFcn: GetProcAddress failed %d\n",
GetLastError());
return(FALSE);
}
return TRUE;
}