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.
 
 
 
 
 
 

2867 lines
83 KiB

/*++
Copyright (c) 1992 Microsoft Corporation
Module Name:
dataman.c
Abstract:
Contains code for the Service Control Database manager. This includes
all the linked list routines. This file contains the following
functions:
ScGetOrderGroupList
ScGetStandaloneGroupList
ScGetServiceDatabase
ScGetUnresolvedDependList
ScActivateServiceRecord
ScCreateImageRecord
ScCreateServiceRecord
ScAddConfigInfoServiceRecord
ScDecrementUseCountAndDelete
ScProcessDeferredList
ScFindEnumStart
ScGetNamedImageRecord
ScGetNamedServiceRecord
ScGetDisplayNamedServiceRecord
ScGetTotalNumberOfRecords
ScInitDatabase
ScEndDatabase
ScProcessCleanup
ScDeleteMarkedServices
ScRemoveService
ScDeleteImageRecord (internal only)
ScDeactivateServiceRecord
ScTerminateServiceProcess (internal only)
ScDatabaseLockFcn
ScGroupListLock
ScUpdateServiceRecordConfig
ScNotifyServiceObject
Author:
Dan Lafferty (danl) 04-Feb-1992
Environment:
User Mode -Win32
Revision History:
12-Jul-1996 AnirudhS
ScDecrementUseCountAndDelete: Don't actually process the deferred
list in this routine, because a number of calling routines assume
that the service record does NOT go away when they call this routine.
Instead, process it when a database lock is released.
25-Jun-1996 AnirudhS
ScProcessCleanup: Fix the use of a freed service record. Don't
try to upgrade shared lock to exclusive, as it can deadlock.
25-Oct-1995 AnirudhS
ScAddConfigInfoServiceRecord: Fix heap corruption bug caused by
security descriptor being freed twice, the second time by
ScProcessDeferredList.
20-Sep-1995 AnirudhS
ScDeleteMarkedServices: Fix heap corruption bug caused by service
record being deleted using LocalFree instead of HeapFree.
26-Jun-1995 AnirudhS
Added ScNotifyServiceObject.
12-Apr-1995 AnirudhS
Added AccountName field to image record.
21-Jan-1994 Danl
ScAddConfigInfoServiceRecord: If no DisplayName, or the DisplayName
is an empty string, or the DisplayName is the same as the
ServiceName, then just point to the ServiceName for the DisplayName.
22-Oct-1993 Danl
Moved Group and Dependency function into groupman.c.
16-Sept-1993 Danl
ScProcessCleanup: Get the shared lock prior to walking through the
database looking for the one to cleanup. Then get the exclusive
lock to modify it. Remove assert.
12-Feb-1993 Danl
ScActivateServiceRecord now increments the UseCount. This is to
balance the fact that we decrement the UseCount when we
deactivate the service record.
28-Aug-1992 Danl
Re-Added ScGetTotalNumberOfRecords function. This is needed
by the ScShutdownAllServices function.
14-Apr-1992 JohnRo
Use SC_ASSERT() macro.
Made changes suggested by PC-LINT.
10-Apr-1992 JohnRo
Use ScImagePathsMatch() to allow mixed-case image names.
Make sure DeleteFlag gets a value when service record is created.
Added some assertion checks.
04-Feb-1992 Danl
created
--*/
//
// INCLUDES
//
#include <nt.h> // for ntrtl.h
#include <ntrtl.h> // DbgPrint prototype
#include <rpc.h> // DataTypes and runtime APIs
#include <nturtl.h> // needed for windows.h when I have nt.h
#include <windows.h> // LocalAlloc
#include <userenv.h> // UnloadUserProfile
#include <stdlib.h> // wide character c runtimes.
#include <tstr.h> // Unicode string macros
#include <winsvc.h> // public Service Controller Interface.
#ifdef _CAIRO_
#include <wtypes.h> // HRESULT
#include <scmso.h> // ScmCallSvcObject
#endif
#include <scdebug.h> // SC_LOG(), SC_ASSERT().
#include <ntrpcp.h> // MIDL_user_allocate
#include <control.h> // SendControl
#include "dataman.h" // dataman structures
#include "scopen.h" // SERVICE_SIGNATURE
#include "scconfig.h" // ScGenerateServiceDB,ScInitSecurityProcess
#include "svcctrl.h" // ScRemoveServiceBits
#include "scsec.h" // ScCreateScServiceObject
#include "account.h" // ScRemoveAccount
#include <sclib.h> // ScImagePathsMatch().
#include "bootcfg.h" // ScDeleteRegTree().
#include <strarray.h> // ScWStrArraySize
#include <svcslib.h> // SvcRemoveWorkItem
//
// Defines and Typedefs
//
typedef struct _DEFER_LIST{
DWORD TotalElements; // size of ServiceRecPtr array
DWORD NumElements; // numElements in array
LPSERVICE_RECORD ServiceRecordPtr[1];// first element in the array
}DEFER_LIST, *LPDEFER_LIST;
//
// Globals
//
//
// These are the linked list heads for each of the databases
// that are maintained.
//
IMAGE_RECORD ImageDatabase;
SERVICE_RECORD ServiceDatabase;
DWORD ScTotalNumServiceRecs;// number of services
//
// Service Record index number. This allows enumeration to be broken
// up into several calls.
//
DWORD ResumeNumber;
//
// This critical section guards access to the global list of
// services that are to have their use counts decremented.
// The counts can only be decremented by a thread that holds both
// the GroupListLock and the DatabaseLock.
//
// The ScGlobalDeferredList points to a structure that contains an
// array of pointers to service records. The first two elements in
// the structure contain the size and number of element information
// about the array.
// If there are no elements in the list, ScGlobalDefferredList
// will contain a NULL pointer.
//
CRITICAL_SECTION ScDeferDelCriticalSection;
LPDEFER_LIST ScDeferredList=NULL;
LONG ScDeferredListWorkItemQueued = FALSE;
//
// ServiceRecord Heap Information
//
// ServiceRecord Heap - is where all the service records are allocated
// from.
// OrderedHash Heap - Service Names can be found via a (very simple) hash
// table. There is an array of pointers (one for each letter of
// alphabet), where each pointer points to the top of an array of
// pointers to service records. All the service records in that array
// will have names beginning with the same letter. The service record
// pointers will be ordered as to the frequency of access.
//
HANDLE ServiceRecordHeap = NULL;
HANDLE OrderedHashHeap = NULL;
//
// Local Function Prototypes
//
VOID
ScProcessDeferredList(
VOID
);
DWORD
ScDeferredListWorkItem(
IN PVOID pContext,
IN DWORD dwWaitStatus
);
//****************************************************************************/
// Miscellaneous Short Functions
//****************************************************************************/
LPSERVICE_RECORD
ScGetServiceDatabase(
VOID
)
{
return ServiceDatabase.Next;
}
/****************************************************************************/
VOID
ScActivateServiceRecord (
IN LPSERVICE_RECORD ServiceRecord,
IN LPIMAGE_RECORD ImageRecord
)
/*++
Routine Description:
This function can be called with or without a pointer to an ImageRecord.
If it is called without the pointer to the ImageRecord, just the
ServiceRecord is initialized to the START_PENDING state, and the UseCount
is incremented.
If it is called with the pointer to the ImageRecord, then the ImageRecord
pointer is added to the ServiceRecord, and the ImageUseCount
is incremented.
Arguments:
ServiceRecord - This is a pointer to the ServiceRecord that is to be
activated.
ImageRecord - This is a pointer to the ImageRecord that the service
record will point to.
Notes:
This routine assumes that the Exclusive database lock has already
been obtained.
Return Value:
returns 0. (It used to return a service count - but it wasn't used
anywhere).
--*/
{
if (ImageRecord == NULL) {
ServiceRecord->ImageRecord = NULL;
ServiceRecord->ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceRecord->ServiceStatus.dwControlsAccepted = 0;
ServiceRecord->ServiceStatus.dwWin32ExitCode = NO_ERROR;
ServiceRecord->ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceRecord->ServiceStatus.dwCheckPoint = 0;
ServiceRecord->ServiceStatus.dwWaitHint = 2000;
ServiceRecord->UseCount++;
SC_LOG2(USECOUNT, "ScActivateServiceRecord: " FORMAT_LPWSTR
" increment USECOUNT=%lu\n",
ServiceRecord->ServiceName,
ServiceRecord->UseCount);
}
else {
//
// Increment the service count in the image record.
//
ServiceRecord->ImageRecord = ImageRecord;
ServiceRecord->ImageRecord->ServiceCount++;
}
return;
}
/****************************************************************************/
DWORD
ScCreateImageRecord (
OUT LPIMAGE_RECORD *ImageRecordPtr,
IN LPWSTR ImageName,
#ifdef _CAIRO_
IN LPWSTR AccountName,
#endif
IN DWORD Pid,
IN HANDLE PipeHandle,
IN HANDLE ProcessHandle,
IN HANDLE TokenHandle,
IN HANDLE ProfileHandle
)
/*++
Routine Description:
This function allocates storage for a new Image Record, and links
it into the Image Record Database. It also initializes all fields
in the record with the passed in information.
Arguments:
ImageRecordPtr - This is a pointer to where the image record pointer
is to be placed.
ImageName - This is a pointer to a NUL terminated string containing
the name of the image file.
AccountName - This is either NULL (to represent the LocalSystem account)
or a pointer to a NUL terminated string containing the name of the
account under which the image was started.
Pid - This is the Process ID for that the image is running in.
PipeHandle - This is a handle to the pipe that is used to communicat
with the image process.
ProcessHandle - This is a handle to the image process object.
TokenHandle - This is a handle to the process's logon token. It
is NULL if the process runs in the LocalSystem context.
Return Value:
NO_ERROR - The operation was successful.
ERROR_NOT_ENOUGH_MEMORY - Unable to allocate buffer for the image
record.
ERROR_LOCKED - Exclusive access to the database could
not be obtained.
Note:
This routine expects the shared database lock to be held upon entry.
--*/
{
LPIMAGE_RECORD imageRecord; // The new image record pointer
LPIMAGE_RECORD record ; // Temporary pointer
LPWSTR stringArea; // String area in allocated buffer.
//
// Allocate space for the new record (including the string)
//
imageRecord = (LPIMAGE_RECORD)LocalAlloc(LMEM_ZEROINIT,
sizeof(IMAGE_RECORD)
+ WCSSIZE(ImageName)
#ifdef _CAIRO_
+ (AccountName ? WCSSIZE(AccountName) : 0)
#endif
);
if (imageRecord == NULL) {
SC_LOG(TRACE,"CreateImageRecord: Local Alloc failure rc=%ld\n",
GetLastError());
return(ERROR_NOT_ENOUGH_MEMORY);
}
//
// Copy the strings into the new buffer space.
//
stringArea = (LPWSTR)(imageRecord + 1);
(VOID) wcscpy (stringArea, ImageName);
imageRecord->ImageName = stringArea;
#ifdef _CAIRO_
if (AccountName) {
stringArea += (wcslen(stringArea) + 1);
(VOID) wcscpy (stringArea, AccountName);
imageRecord->AccountName = stringArea;
}
else {
imageRecord->AccountName = NULL;
}
#endif
//
// Update the rest of the fields in the Image Record
//
imageRecord->Next = NULL;
imageRecord->Pid = Pid;
imageRecord->PipeHandle = PipeHandle;
imageRecord->ProcessHandle = ProcessHandle;
imageRecord->ServiceCount = 0;
imageRecord->TokenHandle = TokenHandle;
imageRecord->ProfileHandle = ProfileHandle;
imageRecord->ObjectWaitHandle = NULL;
//
// Add record to the Image Database linked list.
//
if (!ScDatabaseLock( SC_MAKE_EXCLUSIVE,"Dataman1")) {
(VOID) LocalFree((HLOCAL)imageRecord);
return(ERROR_LOCKED);
}
record = &ImageDatabase;
ADD_TO_LIST(record, imageRecord);
*ImageRecordPtr = imageRecord;
ScDatabaseLock( SC_MAKE_SHARED,"Dataman2");
return(NO_ERROR);
}
/****************************************************************************/
DWORD
ScCreateServiceRecord(
IN LPWSTR ServiceName,
OUT LPSERVICE_RECORD *ServiceRecord
)
/*++
Routine Description:
This function creates a new "inactive" service record and adds it to
the service record list. A resume number is assigned so that it
can be used as a key in enumeration searches.
To initialize the service record with the fields from the registry,
call ScAddConfigInfoServiceRecord.
Arguments:
ServiceName - This is a pointer to the NUL terminated service name
string.
ServiceRecord - Receives a pointer to the service record created and
inserted into the service record list.
Return Value:
NO_ERROR - The operation was successful.
ERROR_NOT_ENOUGH_MEMORY - The call to allocate memory for a new
service record failed.
Note:
This routine assumes that the caller has exclusively acquired the
database lock.
--*/
{
DWORD status = NO_ERROR;
LPSERVICE_RECORD record; // Temporary pointer
LPWSTR nameArea; // NameString area in allocated buffer.
DWORD nameSize; // num bytes in service name.
//
// Allocate the new service record.
//
nameSize = WCSSIZE(ServiceName);
(*ServiceRecord) = (LPSERVICE_RECORD)HeapAlloc(
ServiceRecordHeap,
HEAP_ZERO_MEMORY,
nameSize + sizeof(SERVICE_RECORD)
);
if ((*ServiceRecord) == NULL) {
SC_LOG0(ERROR,"CreateServiceRecord: HeapAlloc failure\n");
return ERROR_NOT_ENOUGH_MEMORY;
}
//
// Copy the ServiceName into the new buffer space.
//
nameArea = (LPWSTR)((LPBYTE)(*ServiceRecord) + sizeof(SERVICE_RECORD));
(VOID) wcscpy (nameArea, ServiceName);
//
// At this point we have the space for a service record, and it
// contains the name of the service.
//
//
// Fill in all the fields that need to be non-zero.
// Note: The display name is initialized to point to the service name.
//
(*ServiceRecord)->ServiceName = nameArea;
(*ServiceRecord)->DisplayName = nameArea;
(*ServiceRecord)->ResumeNum = ResumeNumber++;
(*ServiceRecord)->Signature = SERVICE_SIGNATURE;
(*ServiceRecord)->ImageRecord = NULL;
(*ServiceRecord)->StartDepend = NULL;
(*ServiceRecord)->StopDepend = NULL;
(*ServiceRecord)->ErrorControl = SERVICE_ERROR_NORMAL;
(*ServiceRecord)->StatusFlag = 0;
(*ServiceRecord)->ServiceStatus.dwCurrentState = SERVICE_STOPPED;
(*ServiceRecord)->ServiceStatus.dwWin32ExitCode = ERROR_SERVICE_NEVER_STARTED;
(*ServiceRecord)->StartState = SC_NEVER_STARTED;
//
// Add the service to the service record linked list.
//
record = &ServiceDatabase;
ADD_TO_LIST(record, (*ServiceRecord));
ScTotalNumServiceRecs++;
return(status);
}
/****************************************************************************/
DWORD
ScAddConfigInfoServiceRecord(
IN LPSERVICE_RECORD ServiceRecord,
IN DWORD ServiceType,
IN DWORD StartType,
IN DWORD ErrorControl,
IN LPWSTR Group OPTIONAL,
IN DWORD Tag,
IN LPWSTR Dependencies OPTIONAL,
IN LPWSTR DisplayName OPTIONAL,
IN PSECURITY_DESCRIPTOR Sd OPTIONAL
)
/*++
Routine Description:
This function adds the configuration information to the service
record.
NOTE: This function is called when the service controller is
reading service entries from the registry at startup, as well as
from RCreateServiceW.
Arguments:
ServiceRecord - Pointer to the service record to modify.
DisplayName - A string that is the displayable name for the service.
ServiceType - Indicates whether the ServiceRecord is for a win32 service
or a device driver.
StartType - Specifies when to start the service: automatically at boot or
on demand.
ErrorControl - Specifies the severity of the error if the service fails
to start.
Tag - DWORD identifier for the service. 0 means no tag.
Group - Name of the load order group this service is a member of.
Dependencies - Names of services separated by colon which this service
require to be started before it can run.
Sd - Security descriptor for the service object. If NULL, i.e. could
not read from registry, create a default one.
Return Value:
NO_ERROR - The operation was successful.
ERROR_NOT_ENOUGH_MEMORY - The call to allocate memory for a new
service record or display name failed.
Note:
This routine assumes that the caller has exclusively acquired both the
database lock and the GroupListLock.
If this call is successful, do the following before freeing the memory
of the service record in ScDecrementUseCountAndDelete:
ScDeleteStartDependencies(ServiceRecord);
ScDeleteGroupMembership(ServiceRecord);
ScDeleteRegistryGroupPointer(ServiceRecord);
--*/
{
DWORD status;
LPWSTR ScSubStrings[1];
//
// Fill in the service record.
//
ServiceRecord->StartType = StartType;
ServiceRecord->ServiceStatus.dwServiceType = ServiceType;
ServiceRecord->ErrorControl = ErrorControl;
ServiceRecord->Tag = Tag;
//
// The display name in the service record already points to the
// ServiceName string. If the DisplayName is present and different
// from the ServiceName, then allocate storage for it and copy the
// string there.
//
SC_LOG0(SECURITY,"ScAddConfigInfoServiceRecord: Allocate for display name\n");
if ((DisplayName != NULL) && (*DisplayName != L'\0') &&
(_wcsicmp(DisplayName,ServiceRecord->ServiceName) != 0)) {
ServiceRecord->DisplayName = (LPWSTR)LocalAlloc(
LMEM_FIXED,
WCSSIZE(DisplayName));
if (ServiceRecord->DisplayName == NULL) {
SC_LOG(TRACE,"ScAddConfigInfoServiceRecord: LocalAlloc failure rc=%ld\n",
GetLastError());
return ERROR_NOT_ENOUGH_MEMORY;
}
wcscpy(ServiceRecord->DisplayName,DisplayName);
}
//
// Create a default security descriptor for the service.
//
if (! ARGUMENT_PRESENT(Sd)) {
SC_LOG0(SECURITY,"ScAddConfigInfoServiceRecord: create service obj\n");
if ((status = ScCreateScServiceObject(
&ServiceRecord->ServiceSd
)) != NO_ERROR) {
goto ErrorExit;
}
}
else {
SC_LOG1(SECURITY,
"ScAddConfigInfoServiceRecord: Using " FORMAT_LPWSTR
" descriptor from registry\n", ServiceRecord->ServiceName);
ServiceRecord->ServiceSd = Sd;
}
SC_LOG0(SECURITY,"ScAddConfigInfoServiceRecord: Get Group List Lock\n");
//
// Save the group membership information.
//
SC_LOG0(SECURITY,"ScAddConfigInfoServiceRecord: create group memebership\n");
if ((status = ScCreateGroupMembership(
ServiceRecord,
Group
)) != NO_ERROR) {
goto ErrorExit;
}
SC_LOG0(SECURITY,"ScAddConfigInfoServiceRecord: create Reg Grp Ptr\n");
if ((status = ScCreateRegistryGroupPointer(
ServiceRecord,
Group
)) != NO_ERROR) {
ScDeleteGroupMembership(ServiceRecord);
goto ErrorExit;
}
//
// Don't create dependencies list yet. Just save the string in
// the service record.
//
if ((Dependencies != NULL) && (*Dependencies != 0)) {
//
// If StartType is BOOT_START or SYSTEM_START, it is invalid
// for the service to be dependent on another service. It can
// only be dependent on a group.
//
DWORD DependenciesSize = 0;
DWORD EntryByteCount;
LPWSTR Entry = Dependencies;
while (*Entry != 0) {
if (StartType == SERVICE_BOOT_START ||
StartType == SERVICE_SYSTEM_START) {
if (*Entry != SC_GROUP_IDENTIFIERW) {
SC_LOG1(ERROR, "ScAddConfigInfoServiceRecord: Boot or System "
"start driver " FORMAT_LPWSTR " must depend on a group\n",
ServiceRecord->DisplayName);
ScSubStrings[0] = ServiceRecord->DisplayName;
ScLogEvent(
EVENT_INVALID_DRIVER_DEPENDENCY,
1,
ScSubStrings
);
status = ERROR_INVALID_PARAMETER;
ScDeleteGroupMembership(ServiceRecord);
ScDeleteRegistryGroupPointer(ServiceRecord);
goto ErrorExit;
}
}
EntryByteCount = WCSSIZE(Entry); // This entry and its null.
DependenciesSize += EntryByteCount;
Entry = (LPWSTR) ((DWORD) Entry + EntryByteCount);
}
DependenciesSize += sizeof(WCHAR);
ServiceRecord->Dependencies = (LPWSTR)LocalAlloc(
0,
DependenciesSize
);
if (ServiceRecord->Dependencies == NULL) {
ScDeleteGroupMembership(ServiceRecord);
ScDeleteRegistryGroupPointer(ServiceRecord);
goto ErrorExit;
}
memcpy(ServiceRecord->Dependencies, Dependencies, DependenciesSize);
}
return NO_ERROR;
ErrorExit:
if (DisplayName != NULL) {
LocalFree((HLOCAL)ServiceRecord->DisplayName);
}
(void) RtlDeleteSecurityObject(&ServiceRecord->ServiceSd);
//
// Prevent ScProcessDeferredList from trying to free the same heap block
// again later
//
ServiceRecord->ServiceSd = NULL;
return status;
}
/****************************************************************************/
VOID
ScDecrementUseCountAndDelete(
LPSERVICE_RECORD ServiceRecord
)
/*++
Routine Description:
This function decrements the UseCount for a service, and it the
UseCount reaches zero and the service is marked for deletion,
it puts a pointer to the service record into an array of
such pointers that is stored in a deferred list structure. The
pointer to this structure is stored at the global location called
ScDeferredList. Access to this list is synchronized by use of the
ScDeferDelCriticalSection.
CODEWORK: This critical section is redundant, because the database
lock is held exclusively at all times that the critical section is
entered. Remove it. (AnirudhS 7/12/96)
A large number of routines in the service controller walk the
service database and sometimes call this function, either directly
or indirectly. It would complicate the programming of all those
routines if they all had to assume that service records could get
deleted under them. Therefore, this function never actually deletes
any service record. Instead, the deferred list is processed when
the exclusive DatabaseLock is released.
Before the service can actually be deleted, the GroupListLock must
also be held. To avoid deadlocks, the rule is that when both the
GroupListLock and the DatabaseLock must be held, the GroupListLock
must be obtained first. However, by the time we get down to this
low level routine, we should already have the DatabaseLock, but the
GroupListLock probably has not been obtained because the top level
function couldn't predict that we would end up on this code path.
Therefore, just before the DatabaseLock is relased, if a deferred
list needs to be processed, the DatabaseLock routine attempts to get
the GroupListLock without waiting. If it returns with an error, thus
indicating that some other thread has the GroupListLock, it will allow
the thread that has the GroupListLock to perform the ProcessDeferredList
just prior to giving up the Lock.
NOTE: The caller is expected to hold the Exclusive DatabaseLock
prior to calling this function. If the caller holds the GroupListLock
when this function is called, it should be held with EXCLUSIVE access
only.
The following functions call this routine:
ScDeactivateServiceRecord
RCloseServiceHandle
ScGetDriverStatus
ScStartServiceAndDependencies
Arguments:
ServiceRecord - This is a pointer to the service record that is having
its use count deleted.
Return Value:
none.
--*/
{
DWORD status=NO_ERROR;
DWORD numBytes = 0;
DWORD numElements = 0;
LPDEFER_LIST newList = NULL;
if (ServiceRecord->UseCount == 0) {
//
// The use count should not ever be zero when we enter this routine.
//
SC_LOG1(ERROR,"ScDecrementUseCountAndDelete: Attempt to decrement UseCount beyond zero.\n"
"\t"FORMAT_LPWSTR" \n", ServiceRecord->ServiceName);
SC_ASSERT(FALSE);
}
else {
if ((ServiceRecord->UseCount == 1) &&
(DELETE_FLAG_IS_SET(ServiceRecord))) {
//
// If the use count is one, we have a special case. We want
// to postpone decrementing this last time until we have the
// group list lock.
//
SC_LOG0(LOCKS,"ScDecrementUseCountAndDelete: Entering Defer "
"Critical Section\n");
EnterCriticalSection(&ScDeferDelCriticalSection);
SC_LOG0(LOCKS,"ScDecrementUseCountAndDelete: Entered Defer "
"Critical Section\n");
//
// Put the service record pointer in the list.
//
if (ScDeferredList == NULL) {
numElements = 10;
}
else {
//
// If there isn't room for another element in the list,
// then add enough room for more elements.
//
if (ScDeferredList->NumElements ==
ScDeferredList->TotalElements) {
numElements = ScDeferredList->TotalElements + 4;
}
}
if (numElements != 0) {
//
// calculate the memory allocation size in bytes.
// Keep in mind that the structure contains one element
// for the array already. So we subtract that out of
// our desired number of elements.
//
numBytes = ((numElements-1) * sizeof(LPSERVICE_RECORD)) +
sizeof(DEFER_LIST);
newList = (LPDEFER_LIST)LocalAlloc(LMEM_FIXED,numBytes);
if (newList == NULL) {
status = GetLastError();
SC_LOG(ERROR,"ScDecrementUseCountAndDelete: LocalAlloc "
"failed %d\n",status);
//
// We will skip this new element, and try to process what we
// have.
//
goto SkipNewList;
}
//
// If this is replacing an existing list, then copy the
// old info to the new list.
//
if (ScDeferredList != NULL) {
numBytes = ((ScDeferredList->NumElements - 1) *
sizeof(LPSERVICE_RECORD)) + sizeof(DEFER_LIST);
memcpy((PVOID)newList, (PVOID)ScDeferredList, numBytes);
newList->TotalElements = numElements;
LocalFree((LPVOID)ScDeferredList);
}
else {
newList->NumElements = 0;
newList->TotalElements = numElements;
}
ScDeferredList = newList;
}
SkipNewList:
//
// At this point we have a deferred list that can hold the new element.
//
ScDeferredList->ServiceRecordPtr[ScDeferredList->NumElements] = ServiceRecord;
ScDeferredList->NumElements++;
SC_LOG0(LOCKS,"ScDecrementUseCountAndDelete: Leaving Defer Critical Section\n");
LeaveCriticalSection(&ScDeferDelCriticalSection);
}
else {
//
// If the use count is greater than one, or the service is
// NOT marked for delete, then we want to decrement the use
// count and that is all.
//
ServiceRecord->UseCount--;
SC_LOG2(USECOUNT, "ScDecrementUseCountAndDelete: " FORMAT_LPWSTR
" decrement USECOUNT=%lu\n", ServiceRecord->ServiceName, ServiceRecord->UseCount);
}
}
return;
}
/****************************************************************************/
VOID
ScProcessDeferredList(
VOID
)
/*++
Routine Description:
This function loops through each service record pointer in the
ScDeferredList, and decrements the UseCount for that ServiceRecord.
If that count becomes zero, and if the ServiceRecord is marked
for deletion, This routine will delete the service record and
the registry entry for that service.
This function frees the memory pointed to by ScDeferredList, when
it is done processing the list.
ASSUMPTIONS:
This routine assumes that the ScDeferDelCriticalSection is already
held, and that the GroupListLock has already been obtained.
The following functions call this routine:
ScGroupListLock
Arguments:
none.
Return Value:
none.
--*/
{
DWORD i;
LPSERVICE_RECORD ServiceRecord;
ScDatabaseLock(SC_GET_EXCLUSIVE, "ScProcessDeferredList");
//
// For each element in the list, delete the service information, and
// free up its associated resources.
//
for (i=0; i<ScDeferredList->NumElements; i++) {
ServiceRecord = ScDeferredList->ServiceRecordPtr[i];
if (ServiceRecord->UseCount == 0) {
SC_LOG1(ERROR,"ScProcessDeferredList: Attempt to decrement UseCount beyond zero.\n"
"\t"FORMAT_LPWSTR" \n", ServiceRecord->ServiceName);
SC_ASSERT(FALSE);
}
else {
//
// The use count is not zero, so we want to decrement it.
// NOTE that even though the count was 1 when we put it in
// the deferred list, it may have been incremented in the
// mean-time.
//
ServiceRecord->UseCount--;
SC_LOG2(USECOUNT, "ScProcessDeferredList: " FORMAT_LPWSTR
" decrement USECOUNT=%lu\n", ServiceRecord->ServiceName, ServiceRecord->UseCount);
}
if ((ServiceRecord->UseCount == 0) &&
(DELETE_FLAG_IS_SET(ServiceRecord))) {
SC_LOG1(USECOUNT,"ScProcessDeferredList:DELETING THE ("FORMAT_LPWSTR") SERVICE\n",
ServiceRecord->ServiceName);
//
// Check to see if there is an LSA secret object to delete
//
if (ServiceRecord->ServiceStatus.dwServiceType & SERVICE_WIN32_OWN_PROCESS) {
HKEY ServiceNameKey;
LPWSTR AccountName;
//
// Open the service name key.
//
if (ScOpenServiceConfigKey(
ServiceRecord->ServiceName,
KEY_READ,
FALSE, // Create if missing
&ServiceNameKey
) == NO_ERROR) {
//
// Read the account name from the registry.
//
if (ScReadStartName(
ServiceNameKey,
&AccountName
) == NO_ERROR) {
if (_wcsicmp(AccountName, SC_LOCAL_SYSTEM_USER_NAME) != 0) {
ScRemoveAccount(ServiceRecord->ServiceName);
}
(void) LocalFree((HLOCAL)AccountName);
} // Got the StartName
ScRegCloseKey(ServiceNameKey);
}
} // endif SERVICE_WIN32_OWN_PROCESS
#ifdef _CAIRO_
//
// Notify the service object class code of the change.
// Errors are ignored.
//
ScNotifyServiceObject(
SO_DELETE,
ServiceRecord->ServiceName,
ServiceRecord->DisplayName,
ServiceRecord->ServiceStatus.dwServiceType
);
#endif
if (ServiceRecord->Dependencies != NULL) {
(void) LocalFree((HLOCAL)ServiceRecord->Dependencies);
}
//
// Free up the DisplayName space.
//
if (ServiceRecord->DisplayName != ServiceRecord->ServiceName) {
LocalFree((HLOCAL)ServiceRecord->DisplayName);
}
ScDeleteGroupMembership(ServiceRecord);
ScDeleteRegistryGroupPointer(ServiceRecord);
ScDeleteStartDependencies(ServiceRecord);
ScDeleteStopDependencies(ServiceRecord);
if (ServiceRecord->ServiceSd != NULL) {
RtlDeleteSecurityObject(&ServiceRecord->ServiceSd);
}
//*******************************
// Delete the registry node for
// This service.
//*******************************
ScDeleteRegServiceEntry(ServiceRecord->ServiceName);
REMOVE_FROM_LIST(ServiceRecord);
if (!HeapFree(ServiceRecordHeap,0,(LPVOID)ServiceRecord)) {
SC_LOG0(ERROR,"ProcessDeferredList: HeapFree(ServiceRec) failed\n");
}
} // End If service can be deleted.
} // End for each element in the list.
//
// The deferred list is no longer needed free it.
//
LocalFree((LPVOID)ScDeferredList);
ScDeferredList = NULL;
ScDeferredListWorkItemQueued = FALSE;
ScDatabaseLock(SC_RELEASE, "ScProcessDeferredList");
return;
}
/****************************************************************************/
BOOL
ScFindEnumStart(
IN DWORD ResumeIndex,
OUT LPSERVICE_RECORD *ServiceRecordPtr
)
/*++
Routine Description:
This function finds the first service record to begin the enumeration
search with by finding the next service record folloing the resumeIndex.
Service records are indexed by a ResumeNum value that is stored in
each service record. The numbers increment as the linked list is
walked.
Arguments:
ResumeIndex - This index is compared against the ResumeNum in the
services records. The pointer to the next service record beyond
the ResumeIndex is returned.
ServiceRecordPtr - This is a pointer to a location where the pointer
to the returned service record is to be placed.
Return Value:
TRUE - Indicates that there are service records beyond the resume index.
FALSE - Indicates that there are no service records beyond the resume
index.
Note:
--*/
{
LPSERVICE_RECORD serviceRecord;
serviceRecord = ServiceDatabase.Next;
if (serviceRecord == NULL) {
SC_ASSERT(serviceRecord != NULL);
//
// There are no records in the database for some reason. So
// This will return NO_ERROR with ServicesReturned = 0.
//
return(FALSE);
}
//
// Find the next service record beyond the ResumeHandle.
//
while (serviceRecord->ResumeNum <= ResumeIndex) {
serviceRecord = serviceRecord->Next;
if (serviceRecord == NULL) {
//
// There are no more Service Records beyond
// the resume index.
//
return(FALSE);
}
}
*ServiceRecordPtr = serviceRecord;
return(TRUE);
}
/****************************************************************************/
BOOL
ScGetNamedImageRecord (
IN LPWSTR ImageName,
OUT LPIMAGE_RECORD *ImageRecordPtr
)
/*++
Routine Description:
This function searches for an Image Record that has a name matching
that which is passed in.
Arguments:
ImageName - This is a pointer to a NUL terminated image name string.
This may be in mixed case.
ImageRecordPtr - This is a pointer to a location where the pointer to
the Image Record is to be placed.
Note:
The Database Lock must be held with at least shared access prior to
calling this routine.
Return Value:
TRUE - if the record was found.
FALSE - if the record was not found.
--*/
{
PIMAGE_RECORD imageRecord;
BOOL found;
if (ImageName == NULL) {
SC_LOG(TRACE,"GetNamedImageRecord: Name was NULL\n",0);
return (FALSE);
}
found = FALSE;
//
// Check the database of running images
//
imageRecord = &ImageDatabase;
while (imageRecord->Next != NULL) {
imageRecord = imageRecord->Next;
if (ScImagePathsMatch(imageRecord->ImageName, ImageName)) {
found = TRUE;
break;
}
}
if (found) {
*ImageRecordPtr = imageRecord;
}
return(found);
}
/****************************************************************************/
DWORD
ScGetNamedServiceRecord (
IN LPWSTR ServiceName,
OUT LPSERVICE_RECORD *ServiceRecordPtr
)
/*++
Routine Description:
Uses the service name to look through the service and device linked
lists until it finds a match. Inactive services can be identified by
finding CurrentState = SERVICE_STOPPED.
Arguments:
ServiceName - This is a pointer to a NUL terminated service name string.
ServiceRecordPtr - This is a pointer to a location where the pointer to
the Service Record is to be placed.
Return Value:
NO_ERROR - if the record was found.
ERROR_SERVICE_DOES_NOT_EXIST - if the service record was not found in
the linked list.
ERROR_INVALID_NAME - if the service name was NULL.
Note:
The caller is expected to grab the lock before calling this routine.
--*/
{
PSERVICE_RECORD serviceRecord;
BOOL found;
if (ServiceName == NULL) {
SC_LOG(TRACE,"GetNamedServiceRecord: Name was NULL\n",0);
return (ERROR_INVALID_NAME);
}
found = FALSE;
//
// Check the database of running services
//
serviceRecord = &ServiceDatabase;
while (serviceRecord->Next != NULL) {
serviceRecord = serviceRecord->Next;
if (_wcsicmp(serviceRecord->ServiceName, ServiceName)== 0) {
found = TRUE;
break;
}
}
if (!found) {
return(ERROR_SERVICE_DOES_NOT_EXIST);
}
*ServiceRecordPtr = serviceRecord;
return(NO_ERROR);
}
/****************************************************************************/
DWORD
ScGetDisplayNamedServiceRecord (
IN LPWSTR ServiceDisplayName,
OUT LPSERVICE_RECORD *ServiceRecordPtr
)
/*++
Routine Description:
Uses the service display name to look through the service and device
linked lists until it finds a match.
Arguments:
ServiceDisplayName - This is a pointer to a NUL terminated service
display name string.
ServiceRecordPtr - This is a pointer to a location where the pointer to
the Service Record is to be placed.
Return Value:
NO_ERROR - if the record was found.
ERROR_SERVICE_DOES_NOT_EXIST - if the service record was not found in
the linked list.
ERROR_INVALID_NAME - if the service display name was NULL.
Note:
The caller is expected to grab the lock before calling this routine.
--*/
{
PSERVICE_RECORD serviceRecord;
BOOL found;
if (ServiceDisplayName == NULL) {
SC_LOG(TRACE,"GetNamedServiceRecord: Name was NULL\n",0);
return (ERROR_INVALID_NAME);
}
found = FALSE;
//
// Check the database of running services
//
serviceRecord = &ServiceDatabase;
while (serviceRecord->Next != NULL) {
serviceRecord = serviceRecord->Next;
if (_wcsicmp(serviceRecord->DisplayName, ServiceDisplayName)== 0) {
found = TRUE;
break;
}
}
if (!found) {
return(ERROR_SERVICE_DOES_NOT_EXIST);
}
*ServiceRecordPtr = serviceRecord;
return(NO_ERROR);
}
/****************************************************************************/
DWORD
ScGetTotalNumberOfRecords (VOID)
/*++
Routine Description:
Finds the total number of installed Service Records in the database.
This is used in the Enum case where only the installed services are
enumerated.
Arguments:
none
Return Value:
TotalNumberOfRecords
--*/
{
return(ScTotalNumServiceRecs);
}
/****************************************************************************/
BOOL
ScInitDatabase (VOID)
/*++
Routine Description:
This function initializes the Service Controllers database.
Arguments:
none
Return Value:
TRUE - Initialization was successful
FALSE - Initialization failed
--*/
{
ScTotalNumServiceRecs = 0;
InitializeCriticalSection(&ScDeferDelCriticalSection);
ImageDatabase.Next = NULL;
ImageDatabase.Prev = NULL;
ServiceDatabase.Next = NULL;
ServiceDatabase.Prev = NULL;
ScInitGroupDatabase();
ResumeNumber = 1;
//
// Create the database lock.
// NOTE: This is never deleted. It is assumed it will be deleted
// when the process goes away.
//
ScDatabaseLock(SC_INITIALIZE, "ScInitDatabase");
//
// Initialize the group list lock used for protecting the
// OrderGroupList and StandaloneGroupList
//
ScGroupListLock(SC_INITIALIZE);
//
// This routine does the following:
// - Read the load order group information from the registry.
// - Generate the database of service records from the information
// stored in the registry.
//
if (!ScGenerateServiceDB()) {
return(FALSE);
}
return(TRUE);
}
VOID
ScEndDatabase(
VOID
)
{
LPSERVICE_RECORD Service, Svc;
LPIMAGE_RECORD Image;
LPIMAGE_RECORD Img;
ScGroupListLock(SC_GET_EXCLUSIVE);
ScDatabaseLock(SC_GET_EXCLUSIVE, "ScEndDatabase1");
Service = ServiceDatabase.Next;
while (Service != NULL) {
Svc = Service;
Service = Service->Next;
if (Svc->Dependencies != NULL) {
(void) LocalFree((HLOCAL)Svc->Dependencies);
}
ScDeleteGroupMembership(Svc);
ScDeleteRegistryGroupPointer(Svc);
ScDeleteStartDependencies(Svc);
ScDeleteStopDependencies(Svc);
if (Svc->ServiceSd != NULL) {
RtlDeleteSecurityObject(&Svc->ServiceSd);
}
if (Svc->DisplayName != Svc->ServiceName) {
(VOID) LocalFree((HLOCAL)Svc->DisplayName);
}
REMOVE_FROM_LIST(Svc);
if (!HeapFree(ServiceRecordHeap,0,(LPVOID)Svc)) {
SC_LOG0(ERROR,"ScEndDatabase: HeapFree(ServiceRec) failed\n");
}
}
Image = ImageDatabase.Next;
while (Image != NULL) {
Img = Image;
Image = Image->Next;
ScDeleteImageRecord(Img);
}
if (!HeapDestroy(ServiceRecordHeap)) {
SC_LOG0(ERROR,"ScEndDatabase: HeapDestroy(ServiceRecordHeap) failed\n");
}
ScDatabaseLock(SC_RELEASE, "ScEndDatabase1");
ScDatabaseLock(SC_DELETE, "ScEndDatabase");
ScEndGroupDatabase();
ScGroupListLock(SC_RELEASE);
ScGroupListLock(SC_DELETE);
}
/****************************************************************************/
VOID
ScProcessCleanup(
HANDLE ProcessHandle
)
/*++
Routine Description:
This function is called when a process has died, and the service
record in the database needs cleaning up. This function will
use the ProcessHandle as a key when scanning the ServiceRecord
database. All of the service records referencing that handle
are cleaned up, and then the image record that they reference
is deleted.
In cleaning up a service record, CurrentState is set to
SERVICE_STOPPED, and the ExitCode is set to a unique value that
indicates that the service died unexpectedly and without warning.
Arguments:
ProcessHandle - This is the handle of the process that died
unexpectedly.
Return Value:
none.
--*/
{
LPSERVICE_RECORD serviceRecord = NULL;
LPIMAGE_RECORD imageRecord;
DWORD serviceCount;
//
// Get exclusive use of database so that it can be modified.
//
ScDatabaseLock(SC_GET_EXCLUSIVE,"ScProcessCleanup1");
//
// Look through the Service Records for the first occurrence of
// a service record with the same ProcessHandle.
//
ScFindEnumStart(0, &serviceRecord);
for ( ; serviceRecord != NULL; serviceRecord = serviceRecord->Next)
{
if ((serviceRecord->ImageRecord != NULL) &&
(ProcessHandle == serviceRecord->ImageRecord->ProcessHandle))
{
imageRecord = serviceRecord->ImageRecord;
serviceCount = imageRecord->ServiceCount;
break;
}
}
if (serviceRecord == NULL)
{
SC_LOG(ERROR,
"ScProcessCleanup: Service Record for image cannot be found\n",0);
ScDatabaseLock(SC_RELEASE,"ScProcessCleanup2");
return;
}
// The image record's service count must include at least this service
if (serviceCount == 0)
{
SC_ASSERT(0);
// Do something sensible if this ever does happen
serviceCount = imageRecord->ServiceCount = 1;
}
//
// The Image may have several services running in it.
// Find the service records for all running services in this
// image.
//
// NOTE: If the service is typed as a SERVICE_WIN32_OWN_PROCESS, this
// means that only one service can exist in the process that
// went down. However, the serviceCount should correctly
// indicate as such in that case.
//
for ( ; serviceRecord != NULL; serviceRecord = serviceRecord->Next)
{
if (
(serviceRecord->ImageRecord == imageRecord)
&&
(serviceRecord->ServiceStatus.dwCurrentState != SERVICE_STOPPED)
)
{
serviceRecord->StartError = ERROR_PROCESS_ABORTED;
serviceRecord->StartState = SC_START_FAIL;
serviceRecord->ServiceStatus.dwWin32ExitCode = ERROR_PROCESS_ABORTED;
//
// Clear the server announcement bits in the global location
// for this service.
//
ScRemoveServiceBits(serviceRecord);
serviceCount = ScDeactivateServiceRecord(serviceRecord);
if (serviceCount == 0)
{
// No need to continue
break;
}
}
}
// (If we hit this assert it means that the service database was corrupt:
// the number of service records pointing to this image record was less
// than the service count in the image record. Not much we can do now.)
SC_ASSERT(serviceCount == 0);
//
// Delete the ImageRecord;
//
ScDeleteImageRecord(imageRecord);
ScDatabaseLock(SC_RELEASE,"ScProcessCleanup3");
return;
}
VOID
ScDeleteMarkedServices(
VOID
)
/*++
Routine Description:
This function looks through the service record database for any entries
marked for deletion. If one is found, it is removed from the registry
and its entry is deleted from the service record database.
WARNING:
This function is to be called during initialization only. It
is assumed that no services are running when this function is called.
Therefore, no locks are held during this operation.
Arguments:
none
Return Value:
none
--*/
{
LPSERVICE_RECORD serviceRecord;
LPSERVICE_RECORD saveRecord = NULL;
HKEY ServiceNameKey;
LPWSTR AccountName;
DWORD status = NO_ERROR;
for (serviceRecord = ServiceDatabase.Next;
serviceRecord != NULL;
serviceRecord = serviceRecord->Next) {
if (! DELETE_FLAG_IS_SET(serviceRecord)) {
continue;
}
SC_LOG(TRACE,"ScDeleteMarkedServices: %ws is being deleted\n",
serviceRecord->ServiceName);
//
// Open the service name key.
//
if (ScOpenServiceConfigKey(
serviceRecord->ServiceName,
KEY_READ,
FALSE, // Create if missing
&ServiceNameKey)==NO_ERROR) {
//
// Read the account name from the registry.
// If this fails, we still want to delete the registry entry.
//
if (ScReadStartName(
ServiceNameKey,
&AccountName)==NO_ERROR) {
if (_wcsicmp(AccountName, SC_LOCAL_SYSTEM_USER_NAME) != 0) {
ScRemoveAccount(serviceRecord->ServiceName);
}
(void) LocalFree((HLOCAL)AccountName);
} // Got the StartName
ScRegCloseKey(ServiceNameKey);
//
// Delete the entry from the registry
//
ScDeleteRegServiceEntry(serviceRecord->ServiceName);
//
// Free memory for the DisplayName.
//
if (serviceRecord->DisplayName != serviceRecord->ServiceName) {
LocalFree((HLOCAL)serviceRecord->DisplayName);
}
//
// Remove the service record from the database
//
saveRecord = serviceRecord->Prev;
REMOVE_FROM_LIST(serviceRecord);
if (!HeapFree(ServiceRecordHeap, 0, serviceRecord)) {
SC_LOG(ERROR, "ScDeleteMarkedServices: HeapFree failed %lu\n",
GetLastError());
}
serviceRecord = saveRecord;
}
}
}
/****************************************************************************/
DWORD
ScRemoveService (
IN LPSERVICE_RECORD ServiceRecord
)
/*++
Routine Description:
This should be used to deactivate a service record and shut down
the process only if it is the last service in the process. It
will be used for polite shut-down when a service terminates as
normal. It will always be called by the status routine. If the
service controller believes that no other services are running in
the process, it can force termination of the process if it does
not respond to the process shutdown request.
Another routine "ForcedShutdown" will be called when it is
necessary to shut down a service process. ForcedShutdown will
find all the services in a process and first ask them to
shut down in a clean fashion. If they don't, the process is simply
terminated.
This function deactivates the service record (ScDeactivateServiceRecord) and
checks to see if the ServiceCount has gone to zero. If it has, then
it terminates the service process. When that is complete, it calls
ScDeleteImageRecord to remove the remaining evidence.
Even if an error occurs, and we are unable to terminate the process,
we delete the image record any - just as we would in the case
where it goes away.
Arguments:
ServiceRecord - This is a pointer to the service record that is to
be removed.
Return Value:
NO_ERROR - The operation was successful.
NERR_ServiceKillProc - The service process had to be killed because
it wouldn't terminate when requested. If the process did not
go away - even after being killed (TerminateProcess), this error
message is still returned.
Note:
Uses Exclusive Locks.
This routine expects the Shared Database Lock to be obtained prior
to entry.
--*/
{
DWORD serviceCount;
LPIMAGE_RECORD ImageRecord;
//
// Get exclusive use of database so that it can be modified.
//
ScDatabaseLock(SC_MAKE_EXCLUSIVE,"Dataman7");
ImageRecord = ServiceRecord->ImageRecord;
//
// ImageRecord may be NULL if it had been cleaned up earlier
//
if (ImageRecord != NULL) {
//
// Deactivate the service record.
//
serviceCount = ScDeactivateServiceRecord(ServiceRecord);
if (serviceCount == 0) {
//
// Now we must terminate the Service Process. The return status
// from this call is not very interesting. The calling application
// probably doesn't care how the process died. (whether it died
// cleanly, or had to be killed).
//
ScTerminateServiceProcess(ImageRecord);
ScDeleteImageRecord (ImageRecord);
}
}
//
// Done with modifications - now allow other threads database access.
//
ScDatabaseLock(SC_MAKE_SHARED,"Dataman8");
return(NO_ERROR);
}
/****************************************************************************/
VOID
ScDeleteImageRecord (
IN LPIMAGE_RECORD ImageRecord
)
/*++
Routine Description:
This function deletes an ImageRecord from the database by removing it
from the linked list, and freeing its associated memory. Prior to
doing this however, it closes the PipeHandle and the ProcessHandle
in the record.
Arguments:
ImageRecord - This is a pointer to the ImageRecord that is being deleted.
Return Value:
nothing
Notes:
This routine assumes that the Exclusive database lock has already
been obtained. (ScRemoveService and NetrServiceInstall call this
function).
--*/
{
HANDLE status; // return status from LocalFree
SC_ASSERT( ImageRecord != NULL );
//
// Remove the Image record from linked list.
//
REMOVE_FROM_LIST(ImageRecord);
//
// What else can we do except note the errors in debug mode?
//
if (CloseHandle(ImageRecord->PipeHandle) == FALSE) {
SC_LOG(TRACE,"DeleteImageRecord: ClosePipeHandle Failed %lu\n",
GetLastError());
}
if (CloseHandle(ImageRecord->ProcessHandle) == FALSE) {
SC_LOG(TRACE,"DeleteImageRecord: CloseProcessHandle Failed %lu\n",
GetLastError());
}
if (ImageRecord->ProfileHandle != (HANDLE) NULL) {
if (UnloadUserProfile(ImageRecord->TokenHandle,
ImageRecord->ProfileHandle) == FALSE) {
SC_LOG1(ERROR,"DeleteImageRecord: UnloadUserProfile Failed %lu\n",
GetLastError());
}
}
if (ImageRecord->TokenHandle != (HANDLE) NULL) {
if (CloseHandle(ImageRecord->TokenHandle) == FALSE) {
SC_LOG1(TRACE,"DeleteImageRecord: CloseTokenHandle Failed %lu\n",
GetLastError());
}
}
status = LocalFree((HLOCAL)ImageRecord);
if (status != NULL) {
SC_LOG(TRACE,"DeleteImageRecord: LocalFree Failed, rc = %d\n",
GetLastError());
}
return;
}
/****************************************************************************/
DWORD
ScDeactivateServiceRecord (
IN LPSERVICE_RECORD ServiceRecord
)
/*++
Routine Description:
This function deactivates a service record by updating the proper
GlobalCount data structure.
NOTE: Although the ServiceRecord does not go away, the pointer to
the ImageRecord is destroyed.
Arguments:
ServiceRecord - This is a pointer to the ServiceRecord that is to be
deleted (moved to uninstalled database).
Notes:
This routine assumes that the Exclusive database lock has already
been obtained. (ScRemoveService & ScProcessCleanup call this function).
Return Value:
ServiceCount - This indicates how many services in this service process
are actually installed.
--*/
{
DWORD serviceCount = 0;
DWORD status;
DWORD dwServiceType;
DWORD dwStartType;
DWORD dwErrorControl;
DWORD dwTagId;
LPWSTR lpDependencies = NULL;
LPWSTR lpLoadOrderGroup = NULL;
LPWSTR lpDisplayName = NULL;
SC_LOG(TRACE,"In DeactivateServiceRecord\n",0);
//
// Decrement the service count in the image record.
//
if (ServiceRecord->ImageRecord != NULL) {
serviceCount = --(ServiceRecord->ImageRecord->ServiceCount);
}
ServiceRecord->ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceRecord->ServiceStatus.dwControlsAccepted = 0;
ServiceRecord->ServiceStatus.dwCheckPoint = 0;
ServiceRecord->ServiceStatus.dwWaitHint = 0;
ServiceRecord->ImageRecord = NULL;
//
// If the Update bit is set in the services status flag, we need
// to read the latest registry configuration information into the
// service record. If this fails, all we can do is log the error
// and press on with the existing data in the service record.
//
if (UPDATE_FLAG_IS_SET(ServiceRecord)) {
status = ScReadConfigFromReg(
ServiceRecord,
&dwServiceType,
&dwStartType,
&dwErrorControl,
&dwTagId,
&lpDependencies,
&lpLoadOrderGroup,
&lpDisplayName);
if (status == NO_ERROR) {
status = ScUpdateServiceRecordConfig(
ServiceRecord,
dwServiceType,
dwStartType,
dwErrorControl,
lpLoadOrderGroup,
(LPBYTE)lpDependencies);
}
if (status != NO_ERROR) {
SC_LOG1(ERROR,"ScDeactivateServiceRecord:Attempt to update "
"configuration for stopped service failed\n",status);
//
// ERROR_LOG ErrorLog
//
}
if (lpLoadOrderGroup != NULL){
LocalFree((HLOCAL)lpLoadOrderGroup);
}
if (lpDependencies != NULL) {
LocalFree((HLOCAL)lpDependencies);
}
if (lpDisplayName != NULL) {
LocalFree((HLOCAL)lpDisplayName);
}
CLEAR_UPDATE_FLAG(ServiceRecord);
}
//
// Since the service is no longer running, and no longer has a handle
// to the service, we need to decrement the use count. If the
// count is decremented to zero, and the service is marked for
// deletion, it will get deleted.
//
ScDecrementUseCountAndDelete(ServiceRecord);
return(serviceCount);
}
/****************************************************************************/
DWORD
ScTerminateServiceProcess (
IN PIMAGE_RECORD ImageRecord
)
/*++
Routine Description:
This function sends an SERVICE_STOP control message to the target
ControlDispatcher. Then it uses the process handle to wait for the
service process to terminate.
If the service process fails to terminate with the polite request, it
will be abruptly killed. After killing the process, this routine will
wait on the process handle to make sure it enters the signaled state.
If it doesn't, and the wait times out, we return anyway having given
it our best shot.
Arguments:
ImageRecord - This is a pointer to the Image Record that stores
information about the service that is to be terminated.
Return Value:
NO_ERROR - The operation was successful.
NERR_ServiceKillProc - The service process had to be killed because
it wouldn't terminate when requested. If the process did not
go away - even after being killed (TerminateProcess), this error
message is still returned.
Note:
LOCKS:
This function always operates within an exclusive database lock.
It DOES NOT give up this lock when sending the control to the service
process. We would like all these operations to be atomic.
It must give this up temporarily when it does the pipe transact.
--*/
{
DWORD returnStatus;
DWORD status;
DWORD waitStatus;
returnStatus = NO_ERROR;
if (SvcRemoveWorkItem(ImageRecord->ObjectWaitHandle)) {
ImageRecord->ObjectWaitHandle = NULL;
}
//
// Send Uninstall message to the Service Process
// Note that the ServiceName is NULL when addressing
// the Service Process.
//
SC_LOG(TRACE,"TerminateServiceProcess, sending Control...\n",0);
//
// The database lock is held exclusively while we are sending this
// control. The service process is not expected to send a status
// message back, so it should be ok to do this.
//
status = ScSendControl (
L"", // no service name.
ImageRecord->PipeHandle, // PipeHandle
SERVICE_STOP, // Opcode
NULL, // CmdArgs (pointer to vectors).
0L, // NumArgs
0L); // StatusHandle
if (status == NO_ERROR) {
//
// Control Dispatcher accepted the request - now
// wait for it to shut down.
//
SC_LOG(TRACE,
"TerminateServiceProcess, waiting for process to terminate...\n",0);
waitStatus = WaitForSingleObject (
ImageRecord->ProcessHandle,
TERMINATE_TIMEOUT);
if (waitStatus == WAIT_TIMEOUT) {
SC_LOG2(ERROR,"TerminateServiceProcess: Process %#lx (%ws) did not exit\n",
ImageRecord->Pid, ImageRecord->ImageName);
//
// Process didn't terminate. So Now I have to kill it.
//
TerminateProcess(ImageRecord->ProcessHandle, 0);
waitStatus = WaitForSingleObject (
ImageRecord->ProcessHandle,
TERMINATE_TIMEOUT);
if (waitStatus == WAIT_TIMEOUT) {
SC_LOG(ERROR,"TerminateServiceProcess: Couldn't kill process %#lx\n",
ImageRecord->Pid);
}
returnStatus = NO_ERROR;
}
}
else {
//
// BUGBUG: WILL THIS PATH BE SUPPORTED IN THE NEW SC?
// note: the return status is NO_ERROR.
//
//
// Incorrect Security Access or LocalAlloc Problem
// - this should never happen.
//
// The service process will only fail the uninstall request
// If it thinks it's coming from a process other than the
// Service Controller.
//
SC_LOG3(ERROR,
"TerminateServiceProcess:SendControl to stop process %#lx (%ws) failed, %ld\n",
ImageRecord->Pid, ImageRecord->ImageName, status);
TerminateProcess(ImageRecord->ProcessHandle, 0);
waitStatus = WaitForSingleObject (
ImageRecord->ProcessHandle,
TERMINATE_TIMEOUT);
if (waitStatus == WAIT_TIMEOUT) {
SC_LOG(ERROR,"TerminateServiceProcess: Couldn't kill process\n",0);
}
returnStatus = NO_ERROR;
}
SC_LOG(TRACE,"TerminateServiceProcess, Done terminating Process!\n",0);
return(returnStatus);
}
BOOL
ScDatabaseLockFcn(
IN SC_LOCK_REQUEST request,
IN LPSTR idString
)
/*++
Routine Description:
This routine handles all access to the Service Controller database
lock.
Locking on the Service Controller is handled by using a single lock
on all the databases at once. There seemed to be little gain in
only locking the active database, and allowing other threads access to
the inactive or image databases. Attempting to achieve that granularity
would only serve to complicate the code.
Reading the Database is handled with shared access. This allows several
threads to read the database at the same time.
Writing (or modifying) the database is handled with exclusive access.
This access is not granted if other threads have read access. However,
shared access can be made into exclusive access as long as no other
threads have shared or exclusive access.
WARNING: This routine can modify the database if it is called with
request = SC_RELEASE.
BUGBUG: What return values are expected from some of these Rtl calls?
Is it something of interest?
Arguments:
request - This indicates what should be done with the lock. Lock
requests are listed in dataman.h
idString - This is a string that identifies who is requesting the lock.
This is used for debugging purposes so I can see where in the code
a request is coming from.
Return Value:
none:
--*/
{
BOOL status = TRUE;
static RTL_RESOURCE SC_DatabaseLock;
switch(request) {
case SC_INITIALIZE:
RtlInitializeResource( &SC_DatabaseLock );
break;
case SC_GET_SHARED:
SC_LOG(LOCKS,"%s:Asking for SC Database Lock shared...\n",idString);
status = RtlAcquireResourceShared( &SC_DatabaseLock, TRUE );
SC_LOG(LOCKS,"%s:Acquired SC Database Lock shared\n",idString);
break;
case SC_GET_EXCLUSIVE:
SC_LOG(LOCKS,"%s:Asking for SC Database Lock exclusive...\n",idString);
status = RtlAcquireResourceExclusive( &SC_DatabaseLock, TRUE );
SC_LOG(LOCKS,"%s:Acquired SC Database Lock exclusive\n",idString);
break;
case SC_RELEASE:
SC_LOG(LOCKS,"%s:Releasing SC Database Lock...\n",idString);
//
// If there is a deferred list to be processed, and we currently
// have exclusive access to the lock, process the deferred list
// before releasing the lock.
// If we have acquired exclusive access recursively, do this
// processing only the last time we release the lock.
//
if (ScDeferredList != NULL) {
if (SC_DatabaseLock.NumberOfActive == -1) {
//
// We are releasing our last recursive exclusive access to
// the lock.
// Attempt to get the GroupListLock without waiting.
// If we get it, then release it, and allow that routine to
// do the actual DecrementUseCountAndDelete.
//
if (ScGroupListLock(SC_EXCLUSIVE_NO_WAIT)) {
ScGroupListLock(SC_RELEASE);
}
}
else if (SC_DatabaseLock.NumberOfActive > 0) {
//
// We are releasing shared access to the lock.
// Queue a workitem (start a thread) to process the deferred
// list, if some other thread with shared access didn't
// already.
//
// Note: Since the exclusive lock is held when items are
// added to the deferred list, this can only occur if the
// thread that wrote the deferred list performed an
// SC_MAKE_SHARED. This code could be moved to the
// SC_MAKE_SHARED case, so that fewer threads have to execute
// it. If that is done, the ScDeferredListWorkItemQueued will
// still be needed, but the InterlockedExchange won't.
// If SC_MAKE_SHARED is removed, this code can be removed as
// well.
//
if (! InterlockedExchange(&ScDeferredListWorkItemQueued, TRUE))
{
HANDLE hWorkItem = SvcAddWorkItem(
NULL, // waitable object
ScDeferredListWorkItem, // callback function
NULL, // context
SVC_QUEUE_WORK_ITEM, // flags
INFINITE, // timeout
NULL // hDllReference
);
if (hWorkItem == NULL)
{
SC_LOG(ERROR,"ScDatabaseLockFcn: SvcAddWorkItem failed %lu\n",
GetLastError());
ScDeferredListWorkItemQueued = FALSE;
}
else
{
SC_LOG(LOCKS,"Work item %#lx will process deferred list\n",
hWorkItem);
}
}
}
}
RtlReleaseResource( &SC_DatabaseLock );
SC_LOG(LOCKS,"%s:Released SC Database Lock\n",idString);
break;
case SC_DELETE:
RtlDeleteResource( &SC_DatabaseLock );
break;
case SC_MAKE_SHARED:
SC_LOG(LOCKS,"%s:Converting SC Database Lock to Shared...\n",idString);
RtlConvertExclusiveToShared( &SC_DatabaseLock );
SC_LOG(LOCKS,"%s:Converted SC Database Lock to Shared\n",idString);
break;
case SC_MAKE_EXCLUSIVE:
// CODEWORK: Remove this option as it can cause deadlocks.
SC_LOG(LOCKS,"%s:Converting SC Database Lock to Exclusive...\n",idString);
RtlConvertSharedToExclusive( &SC_DatabaseLock );
SC_LOG(LOCKS,"%s:Converted SC Database Lock to Exclusive\n",idString);
break;
default:
break;
}
UNREFERENCED_PARAMETER(request);
UNREFERENCED_PARAMETER(idString); // For non-debug version when SC_LOG expands to nothing.
return(status);
}
BOOL
ScGroupListLock(
SC_LOCK_REQUEST request
)
{
BOOL status = TRUE;
static RTL_RESOURCE SC_GroupListLock;
switch (request) {
case SC_INITIALIZE:
RtlInitializeResource( &SC_GroupListLock );
break;
case SC_GET_SHARED:
SC_LOG0(LOCKS,"Asking for SC GroupList Lock shared...\n");
status = RtlAcquireResourceShared( &SC_GroupListLock, TRUE );
SC_LOG0(LOCKS,"Acquired SC GroupList Lock shared\n");
break;
case SC_GET_EXCLUSIVE:
SC_LOG0(LOCKS,"Asking for SC GroupList Lock exclusive...\n");
status = RtlAcquireResourceExclusive( &SC_GroupListLock, TRUE );
SC_LOG0(LOCKS,"Acquired SC GroupList Lock exclusive\n");
break;
case SC_EXCLUSIVE_NO_WAIT:
SC_LOG0(LOCKS,"Asking for SC GroupList Lock exclusive...\n");
status = RtlAcquireResourceExclusive( &SC_GroupListLock, FALSE);
SC_LOG0(LOCKS,"Acquired SC GroupList Lock exclusive\n");
break;
case SC_RELEASE:
//
// BUGBUG : How can we verify that the GroupListLock is actually
// held.
//
SC_LOG0(LOCKS,"ScGroupListLock: Entering Defer Critical Section\n");
EnterCriticalSection(&ScDeferDelCriticalSection);
SC_LOG0(LOCKS,"ScGroupListLock: Entered Defer Critical Section\n");
SC_LOG0(LOCKS,"Clean up deferred UseCount decrements\n");
if (ScDeferredList != NULL) {
ScProcessDeferredList();
}
SC_LOG0(LOCKS,"Releasing SC GroupList Lock...\n");
RtlReleaseResource( &SC_GroupListLock );
SC_LOG0(LOCKS,"Released SC GroupList Lock\n");
SC_LOG0(LOCKS,"ScGroupListLock: Leaving Defer Critical Section\n");
LeaveCriticalSection(&ScDeferDelCriticalSection);
break;
case SC_DELETE:
RtlDeleteResource( &SC_GroupListLock );
break;
case SC_MAKE_SHARED:
SC_LOG0(LOCKS,"Converting SC GroupList Lock to Shared...\n");
RtlConvertExclusiveToShared( &SC_GroupListLock );
SC_LOG0(LOCKS,"Converted SC GroupList Lock to Shared\n");
break;
case SC_MAKE_EXCLUSIVE:
SC_LOG0(LOCKS,"Converting SC GroupList Lock to Exclusive...\n");
RtlConvertSharedToExclusive( &SC_GroupListLock );
SC_LOG0(LOCKS,"Converted SC GroupList Lock to Exclusive\n");
break;
default:
break;
}
return status;
}
DWORD
ScDeferredListWorkItem(
IN PVOID pContext,
IN DWORD dwWaitStatus
)
/*++
Routine Description:
This function acquires and releases the group list lock, and allows
the group list lock routine to process the deferred list.
--*/
{
SC_LOG0(LOCKS, "In ScDeferredListWorkItem, waiting for locks\n");
ScGroupListLock(SC_GET_EXCLUSIVE);
ScGroupListLock(SC_RELEASE);
SC_LOG0(LOCKS, "Returning from ScDeferredListWorkItem\n");
return 0;
}
DWORD
ScUpdateServiceRecordConfig(
IN LPSERVICE_RECORD ServiceRecord,
IN DWORD dwServiceType,
IN DWORD dwStartType,
IN DWORD dwErrorControl,
IN LPWSTR lpLoadOrderGroup,
IN LPBYTE lpDependencies
)
/*++
Routine Description:
This function updates the service record with the latest config
information (passed in).
It assumed that exclusive locks are held before calling this function.
Arguments:
Return Value:
Note:
--*/
#define SERVICE_TYPE_CHANGED 0x00000001
#define START_TYPE_CHANGED 0x00000002
#define ERROR_CONTROL_CHANGED 0x00000004
#define BINARY_PATH_CHANGED 0x00000008
#define LOAD_ORDER_CHANGED 0x00000010
#define TAG_ID_CHANGED 0x00000020
#define DEPENDENCIES_CHANGED 0x00000040
#define START_NAME_CHANGED 0x00000080
{
DWORD status;
DWORD backoutStatus;
LPWSTR OldLoadOrderGroup = NULL;
LPWSTR OldDependencies = NULL;
DWORD OldServiceType;
DWORD OldStartType;
DWORD OldErrorControl;
DWORD Progress = 0;
DWORD bufSize;
DWORD MaxDependSize = 0;
OldServiceType = ServiceRecord->ServiceStatus.dwServiceType;
OldStartType = ServiceRecord->StartType;
OldErrorControl= ServiceRecord->ErrorControl;
//==============================
// UPDATE DWORDs
//==============================
if (dwServiceType != SERVICE_NO_CHANGE) {
ServiceRecord->ServiceStatus.dwServiceType = dwServiceType;
}
if (dwStartType != SERVICE_NO_CHANGE) {
ServiceRecord->StartType = dwStartType;
}
if (dwErrorControl != SERVICE_NO_CHANGE) {
ServiceRecord->ErrorControl = dwErrorControl;
}
Progress |= (SERVICE_TYPE_CHANGED |
START_TYPE_CHANGED |
ERROR_CONTROL_CHANGED );
//==============================
// UPDATE Dependencies
//==============================
if (lpDependencies != NULL) {
//
// Generate the current (old) list of dependency strings.
//
ScGetDependencySize(ServiceRecord,&bufSize, &MaxDependSize);
if (bufSize > 0) {
OldDependencies = (LPWSTR)LocalAlloc(LMEM_FIXED, bufSize);
if (OldDependencies == NULL) {
status = GetLastError();
goto Cleanup;
}
status = ScGetDependencyString(
ServiceRecord,
MaxDependSize,
bufSize,
OldDependencies);
if (status != NO_ERROR) {
goto Cleanup;
}
}
ScDeleteStartDependencies(ServiceRecord);
status = ScCreateDependencies(ServiceRecord, (LPWSTR) lpDependencies);
if (status != NO_ERROR) {
goto Cleanup;
}
Progress |= DEPENDENCIES_CHANGED;
}
//==============================
// UPDATE LoadOrderGroup
//==============================
if (lpLoadOrderGroup != NULL) {
if (*lpLoadOrderGroup != 0) {
//
// The string in lpLoadOrderGroup should match that in
// the RegistryGroup in the service record.
//
if (_wcsicmp(lpLoadOrderGroup, ServiceRecord->RegistryGroup->GroupName) != 0) {
SC_LOG2(ERROR,"ScUpdateServiceRecordConfig: New Group [%ws] Doesn't "
"match that stored in the service database [%ws]\n",
lpLoadOrderGroup,
ServiceRecord->RegistryGroup->GroupName);
status = ERROR_GEN_FAILURE;
goto Cleanup;
}
}
//
// Save Old MemberOfGroup name for error recovery
//
if (ServiceRecord->MemberOfGroup != NULL) {
OldLoadOrderGroup = (LPWSTR)LocalAlloc(
LMEM_FIXED,
WCSSIZE(ServiceRecord->MemberOfGroup->GroupName));
//
// If this allocation fails, just pretend that it doesn't exist.
//
if (OldLoadOrderGroup != NULL) {
wcscpy(OldLoadOrderGroup, ServiceRecord->MemberOfGroup->GroupName);
}
}
//
// Delete MemberOfGroup & Add RegistryGroup to MemberOfGroup so that
// they are the same.
// REMEMBER that RegistryGroup and lpLoadOrderGroup are the same!
//
ScDeleteGroupMembership(ServiceRecord);
status = ScCreateGroupMembership(ServiceRecord, lpLoadOrderGroup);
if (status != NO_ERROR) {
ScDeleteGroupMembership(ServiceRecord);
if ((OldLoadOrderGroup != NULL) && (*OldLoadOrderGroup)) {
backoutStatus = ScCreateGroupMembership(
ServiceRecord,
OldLoadOrderGroup);
if (backoutStatus != NO_ERROR) {
// Do what? - we may want to write to ERROR LOG?
}
}
goto Cleanup;
}
}
status = NO_ERROR;
Cleanup:
if (status != NO_ERROR) {
ServiceRecord->ServiceStatus.dwServiceType = OldServiceType;
ServiceRecord->StartType = OldStartType;
ServiceRecord->ErrorControl = OldErrorControl;
if (Progress & DEPENDENCIES_CHANGED) {
ScDeleteStartDependencies(ServiceRecord);
if ((OldDependencies != NULL) && (*OldDependencies != 0)) {
backoutStatus = ScCreateDependencies(
ServiceRecord,
OldDependencies);
if (backoutStatus != NO_ERROR) {
// Do what? - we may want to write to ERROR LOG?
}
}
}
}
if (OldDependencies != NULL) {
LocalFree((HLOCAL)OldDependencies);
}
if (OldLoadOrderGroup != NULL) {
LocalFree((HLOCAL)OldLoadOrderGroup);
}
return(status);
}
BOOL
ScAllocateSRHeap(
DWORD HeapSize
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
ServiceRecordHeap = HeapCreate(0,HeapSize,0);
if (ServiceRecordHeap == NULL) {
SC_LOG0(ERROR,"Could not allocate Heap for Service Database\n");
return(FALSE);
}
return(TRUE);
}
#ifdef _CAIRO_
VOID
ScNotifyServiceObject(
DWORD dwCallType,
LPWSTR pszSvcName,
LPWSTR pszSvcDisplayName,
DWORD dwServiceType
)
/*++
Routine Description:
This routine notifies the Directory Service of changes in the service
database so that it can keep the service objects in sync with the services
in the database.
The service database must have been locked for read access before
calling this routine.
Arguments:
Return Value:
Notes:
The DLL is linked to dynamically to avoid circular build dependencies.
It is loaded the first time it is called, and never unloaded.
--*/
{
static HINSTANCE hinst = NULL; // module handle
SOCALLBACK pfnNotifyServiceObject; // function pointer
HRESULT hr;
//
// Don't perform operations on service objects during setup, because
// the directory service has not been setup yet
//
if (SetupInProgress(NULL))
{
SC_LOG0(TRACE,"Setup in progress, not creating service objects\n");
return;
}
//
// Service objects are not created for drivers, so don't call the DS.
// Note: We don't handle cases where the service type changes from
// Win32 to driver; instead we leave the service object around to be
// cleaned up at the next boot. (BUGBUG re-examine after SO code stabilizes)
//
if ((dwCallType != SO_INIT) && (dwServiceType & SERVICE_DRIVER))
{
return;
}
if (hinst == NULL)
{
hinst = LoadLibrary(DS_LIB_NAME);
if (hinst == NULL)
{
SC_LOG2(ERROR, "Couldn't load %ws, error %lu\n", DS_LIB_NAME, GetLastError());
return;
}
}
pfnNotifyServiceObject = (SOCALLBACK) GetProcAddress(hinst, SO_CALL_BACK);
if (pfnNotifyServiceObject == NULL)
{
SC_LOG2(ERROR, "Couldn't find " SO_CALL_BACK " in %ws, error %lu\n",
DS_LIB_NAME, GetLastError());
return;
}
if (dwCallType == SO_INIT)
{
LPSERVICE_RECORD Service;
SC_ASSERT(!pszSvcName && !pszSvcDisplayName);
// For each service in the database
for (Service = ScGetServiceDatabase();
Service != NULL;
Service = Service->Next)
{
// Don't create service objects for drivers
if (Service->ServiceStatus.dwServiceType & SERVICE_DRIVER)
{
continue;
}
// We can't pass a null display name to the function, it will AV
SC_ASSERT(Service->ServiceName);
hr = pfnNotifyServiceObject(
SO_CREATE,
Service->ServiceName,
Service->DisplayName ? Service->DisplayName
: Service->ServiceName
);
if (FAILED(hr))
{
SC_LOG3(ERROR, SO_CALL_BACK " for %ws service (%ws) failed %#lx\n",
Service->ServiceName, Service->DisplayName, hr);
}
}
// End of list
hr = pfnNotifyServiceObject(SO_CREATE, NULL, NULL);
if (FAILED(hr))
{
SC_LOG(ERROR,SO_CALL_BACK " for end of service list returned %#lx\n",hr);
}
}
else
{
// We can't pass a null display name to the function, it will AV
SC_ASSERT(pszSvcName);
hr = pfnNotifyServiceObject(
dwCallType,
pszSvcName,
pszSvcDisplayName ? pszSvcDisplayName : pszSvcName);
if (FAILED(hr))
{
SC_LOG4(ERROR, SO_CALL_BACK "(%lu) for %ws service (%ws) failed %#lx\n",
dwCallType, pszSvcName, pszSvcDisplayName, hr);
}
}
return;
}
#endif // _CAIRO_