mirror of https://github.com/tongzx/nt5src
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.
2533 lines
72 KiB
2533 lines
72 KiB
/*++
|
|
|
|
Copyright (c) 1992 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
dataman.cxx
|
|
|
|
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
|
|
ScFreeServiceRecord
|
|
ScAddConfigInfoServiceRecord
|
|
ScDecrementUseCountAndDelete
|
|
ScProcessDeferredList
|
|
ScFindEnumStart
|
|
ScGetNamedImageRecord
|
|
ScGetNamedServiceRecord
|
|
ScGetDisplayNamedServiceRecord
|
|
ScGetTotalNumberOfRecords
|
|
ScInitDatabase
|
|
ScProcessCleanup
|
|
ScDeleteMarkedServices
|
|
|
|
ScRemoveService
|
|
ScDeleteImageRecord (internal only)
|
|
ScDeactivateServiceRecord
|
|
ScTerminateServiceProcess (internal only)
|
|
ScUpdateServiceRecordConfig
|
|
|
|
ScNotifyServiceObject
|
|
|
|
Author:
|
|
|
|
Dan Lafferty (danl) 04-Feb-1992
|
|
|
|
Environment:
|
|
|
|
User Mode -Win32
|
|
|
|
Revision History:
|
|
|
|
22-Oct-1998 jschwart
|
|
Convert SCM to use NT thread pool APIs
|
|
22-Oct-1997 JSchwart (after AnirudhS in _CAIRO_ 12-Apr-1995)
|
|
Enabled changes made in ifdef _CAIRO_ on 12-Apr-1995.
|
|
Removed ScNotifyServiceObject.
|
|
08-Jan-1997 AnirudhS
|
|
Replaced ScDatabaseLockFcn and ScGroupListLock with the new
|
|
locking scheme in lock.cxx. Rewrote ScDeferredList processing.
|
|
04-Dec-1996 AnirudhS
|
|
Added calls to service crash recovery code.
|
|
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 "precomp.hxx"
|
|
#include <userenv.h> // UnloadUserProfile
|
|
#include <stdlib.h> // wide character c runtimes.
|
|
#include <ntrpcp.h> // MIDL_user_allocate
|
|
#include <control.h> // SendControl
|
|
#include "scconfig.h" // ScGenerateServiceDB,ScInitSecurityProcess
|
|
#include "scsec.h" // ScCreateScServiceObject
|
|
#include "account.h" // ScRemoveAccount
|
|
#include <sclib.h> // ScImagePathsMatch().
|
|
#include "bootcfg.h" // ScDeleteRegTree().
|
|
#include <strarray.h> // ScWStrArraySize
|
|
|
|
extern "C" {
|
|
|
|
#include <cfgmgr32.h>
|
|
#include "cfgmgrp.h" // RegisterServiceNotification
|
|
|
|
}
|
|
|
|
//
|
|
// Macros
|
|
//
|
|
|
|
// for every service record in the database...
|
|
// (a slightly more efficient definition of this macro for use within
|
|
// this file)
|
|
//
|
|
#undef FOR_ALL_SERVICES
|
|
#define FOR_ALL_SERVICES(SR) \
|
|
SC_ASSERT(ScServiceListLock.Have()); \
|
|
for (LPSERVICE_RECORD SR = ServiceDatabase.Next; \
|
|
SR != NULL; \
|
|
SR = SR->Next)
|
|
|
|
|
|
//
|
|
// Defines and Typedefs
|
|
//
|
|
class DEFER_LIST
|
|
{
|
|
public:
|
|
VOID Add (LPSERVICE_RECORD ServiceRecord);
|
|
VOID Process();
|
|
|
|
private:
|
|
DWORD TotalElements; // size of ServiceRecPtr array
|
|
DWORD NumElements; // numElements in array
|
|
LPSERVICE_RECORD * ServiceRecordPtr; // pointer to array
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
// 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;
|
|
|
|
//
|
|
// 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.
|
|
// Access to the list is guarded by ScServiceRecordLock.
|
|
//
|
|
DEFER_LIST ScDeferredList;
|
|
|
|
//
|
|
// 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
|
|
ScDeferredListWorkItem(
|
|
IN PVOID pContext
|
|
);
|
|
|
|
|
|
//****************************************************************************/
|
|
// 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).
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
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,
|
|
IN LPWSTR AccountName,
|
|
IN DWORD Pid,
|
|
IN HANDLE PipeHandle,
|
|
IN HANDLE ProcessHandle,
|
|
IN HANDLE TokenHandle,
|
|
IN HANDLE ProfileHandle,
|
|
IN DWORD ImageFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
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.
|
|
|
|
ProfileHandle -
|
|
|
|
ImageFlags -
|
|
|
|
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 temporarily acquires the exclusive database lock.
|
|
|
|
--*/
|
|
{
|
|
LPIMAGE_RECORD imageRecord; // The new image record pointer
|
|
LPIMAGE_RECORD record ; // Temporary pointer
|
|
LPWSTR stringArea; // String area in allocated buffer.
|
|
|
|
TOKEN_STATISTICS TokenStats;
|
|
DWORD dwError;
|
|
|
|
//
|
|
// Allocate space for the new record (including the string)
|
|
//
|
|
|
|
imageRecord = (LPIMAGE_RECORD)LocalAlloc(LMEM_ZEROINIT,
|
|
sizeof(IMAGE_RECORD)
|
|
+ WCSSIZE(ImageName)
|
|
+ (AccountName ? WCSSIZE(AccountName) : 0)
|
|
);
|
|
|
|
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;
|
|
|
|
if (AccountName)
|
|
{
|
|
stringArea += (wcslen(stringArea) + 1);
|
|
(VOID) wcscpy (stringArea, AccountName);
|
|
imageRecord->AccountName = stringArea;
|
|
}
|
|
else
|
|
{
|
|
imageRecord->AccountName = NULL;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
imageRecord->ImageFlags = ImageFlags;
|
|
|
|
if (TokenHandle == NULL)
|
|
{
|
|
LUID SystemLuid = SYSTEM_LUID;
|
|
|
|
RtlCopyLuid(&imageRecord->AccountLuid, &SystemLuid);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Get the unique session ID for this process
|
|
//
|
|
if (!GetTokenInformation(TokenHandle,
|
|
TokenStatistics, // Information wanted
|
|
&TokenStats,
|
|
sizeof(TokenStats), // Buffer size
|
|
&dwError)) // Size required
|
|
{
|
|
dwError = GetLastError();
|
|
|
|
SC_LOG1(ERROR,
|
|
"ScCreateImageRecord: GetTokenInformation FAILED %d\n",
|
|
dwError);
|
|
|
|
LocalFree(imageRecord);
|
|
return dwError;
|
|
}
|
|
|
|
imageRecord->AccountLuid.LowPart = TokenStats.AuthenticationId.LowPart;
|
|
imageRecord->AccountLuid.HighPart = TokenStats.AuthenticationId.HighPart;
|
|
}
|
|
|
|
//
|
|
// Add record to the Image Database linked list.
|
|
//
|
|
|
|
CServiceRecordExclusiveLock Lock;
|
|
|
|
record = &ImageDatabase;
|
|
ADD_TO_LIST(record, imageRecord);
|
|
|
|
*ImageRecordPtr = imageRecord;
|
|
|
|
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.
|
|
|
|
SC_ASSERT(ScServiceListLock.HaveExclusive());
|
|
|
|
// (The service record lock is not needed here since we don't touch
|
|
// the service record after adding it to the service list)
|
|
|
|
//
|
|
// Allocate the new service record.
|
|
//
|
|
nameSize = (DWORD) 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);
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
VOID
|
|
ScFreeServiceRecord(
|
|
IN LPSERVICE_RECORD ServiceRecord
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function frees up a service record that has already been removed
|
|
from the service record list.
|
|
|
|
Arguments:
|
|
|
|
ServiceRecord - Receives a pointer to the service record to free.
|
|
|
|
--*/
|
|
{
|
|
if (!HeapFree(ServiceRecordHeap, 0, ServiceRecord))
|
|
{
|
|
SC_LOG2(ERROR,
|
|
"ScFreeServiceRecord: HeapFree for %ws service failed %d\n",
|
|
ServiceRecord->ServiceName,
|
|
GetLastError());
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
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;
|
|
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
// N.B. This check has been removed. There doesn't seem to be
|
|
// any point in enforcing it, and remote boot needs to
|
|
// allow NetBT to be dependent Tcpip.
|
|
//
|
|
|
|
DWORD DependenciesSize = 0;
|
|
DWORD EntryByteCount;
|
|
LPWSTR Entry = Dependencies;
|
|
|
|
|
|
while (*Entry != 0)
|
|
{
|
|
#if 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);
|
|
|
|
ScEvent(
|
|
EVENT_INVALID_DRIVER_DEPENDENCY,
|
|
ServiceRecord->DisplayName
|
|
);
|
|
|
|
status = ERROR_INVALID_PARAMETER;
|
|
|
|
ScDeleteGroupMembership(ServiceRecord);
|
|
ScDeleteRegistryGroupPointer(ServiceRecord);
|
|
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
EntryByteCount = (DWORD) WCSSIZE(Entry); // This entry and its null.
|
|
DependenciesSize += EntryByteCount;
|
|
|
|
Entry = (LPWSTR) ((DWORD_PTR) Entry + EntryByteCount);
|
|
}
|
|
|
|
DependenciesSize += sizeof(WCHAR);
|
|
|
|
ServiceRecord->Dependencies = (LPWSTR)LocalAlloc(
|
|
0,
|
|
DependenciesSize
|
|
);
|
|
|
|
if (ServiceRecord->Dependencies == NULL)
|
|
{
|
|
ScDeleteGroupMembership(ServiceRecord);
|
|
ScDeleteRegistryGroupPointer(ServiceRecord);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
RtlCopyMemory(ServiceRecord->Dependencies, Dependencies, DependenciesSize);
|
|
}
|
|
|
|
return NO_ERROR;
|
|
|
|
ErrorExit:
|
|
if (DisplayName != NULL)
|
|
{
|
|
LocalFree(ServiceRecord->DisplayName);
|
|
}
|
|
|
|
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
|
|
ScServiceRecordLock.
|
|
|
|
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, it starts a separate thread that first
|
|
acquires all three locks exclusively and then processes the deferred
|
|
list.
|
|
|
|
Note: By jumping through hoops, it is possible to avoid creating
|
|
that thread in certain cases, but since service deletion is a rather
|
|
rare operation, it was not considered worthwhile to create very-
|
|
difficult-to-maintain code to optimize it.
|
|
|
|
|
|
NOTE: The caller is expected to hold the Exclusive DatabaseLock
|
|
prior to calling this function.
|
|
|
|
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.
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
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.
|
|
//
|
|
|
|
//
|
|
// Put the service record pointer in the list, and start a
|
|
// separate thread to process the list.
|
|
//
|
|
ScDeferredList.Add(ServiceRecord);
|
|
}
|
|
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
|
|
DEFER_LIST::Add(
|
|
LPSERVICE_RECORD ServiceRecord
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function adds a service record to the list of service records to
|
|
be deleted. It then starts a thread that will acquire all the locks
|
|
and perform the actual deletion, if such a thread hasn't already been
|
|
started.
|
|
|
|
ScServiceRecordLock is used to ensure that only one thread accesses
|
|
the deferred list at a time.
|
|
|
|
The following functions call this routine:
|
|
ScDecrementUseCountAndDelete
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
if (NumElements + 1 > TotalElements)
|
|
{
|
|
//
|
|
// Reallocate the array with a bit more room
|
|
//
|
|
DWORD NewTotalElements = TotalElements + 4;
|
|
LPSERVICE_RECORD * NewArray = (LPSERVICE_RECORD *)
|
|
LocalAlloc(0, NewTotalElements * sizeof(LPSERVICE_RECORD));
|
|
if (NewArray == NULL)
|
|
{
|
|
SC_LOG(ERROR, "DEFER_LIST::Add: LocalAlloc FAILED %lu\n", GetLastError());
|
|
return;
|
|
}
|
|
|
|
if (ServiceRecordPtr != NULL)
|
|
{
|
|
RtlCopyMemory(NewArray, ServiceRecordPtr,
|
|
NumElements * sizeof(LPSERVICE_RECORD));
|
|
LocalFree(ServiceRecordPtr);
|
|
}
|
|
|
|
TotalElements = NewTotalElements;
|
|
ServiceRecordPtr = NewArray;
|
|
}
|
|
|
|
//
|
|
// At this point we have a deferred list that can hold the new element.
|
|
//
|
|
|
|
ServiceRecordPtr[NumElements] = ServiceRecord;
|
|
NumElements++;
|
|
SC_LOG(LOCKS, "Added %ws service to deferred list\n",
|
|
ServiceRecord->ServiceName);
|
|
|
|
//
|
|
// If we created the deferred list, queue a workitem (start a
|
|
// thread) to process it.
|
|
//
|
|
if (NumElements == 1)
|
|
{
|
|
NTSTATUS ntStatus;
|
|
|
|
ntStatus = RtlQueueWorkItem(ScDeferredListWorkItem, // callback function
|
|
NULL, // context
|
|
WT_EXECUTEONLYONCE); // flags
|
|
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
SC_LOG(ERROR,"Couldn't add DeferredListWorkItem, 0x%x\n",
|
|
ntStatus);
|
|
}
|
|
else
|
|
{
|
|
SC_LOG0(LOCKS,"Work item will process deferred list\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************/
|
|
VOID
|
|
DEFER_LIST::Process(
|
|
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.
|
|
|
|
This routine acquires all 3 database locks.
|
|
The following functions call this routine:
|
|
CGroupListLock::Release
|
|
|
|
Arguments:
|
|
|
|
none.
|
|
|
|
Return Value:
|
|
|
|
none.
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Wait until we have acquired all 3 locks in the proper order
|
|
//
|
|
SC_LOG0(LOCKS, "In ScProcessDeferredList, waiting for locks\n");
|
|
CGroupListExclusiveLock GLock;
|
|
CServiceListExclusiveLock LLock;
|
|
CServiceRecordExclusiveLock RLock;
|
|
|
|
//
|
|
// For each element in the list, delete the service information, and
|
|
// free up its associated resources.
|
|
//
|
|
for (DWORD i=0; i<NumElements; i++)
|
|
{
|
|
LPSERVICE_RECORD ServiceRecord = 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.
|
|
// CODEWORK: Why doesn't ScDecrementUseCountAndDelete just
|
|
// decrement the UseCount to zero itself? If it did, we
|
|
// wouldn't need RLock at all here, just LLock.
|
|
//
|
|
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 (AccountName != NULL
|
|
&&
|
|
_wcsicmp(AccountName, SC_LOCAL_SYSTEM_USER_NAME) != 0)
|
|
{
|
|
ScRemoveAccount(ServiceRecord->ServiceName);
|
|
}
|
|
|
|
LocalFree(AccountName);
|
|
|
|
} // Got the StartName
|
|
|
|
ScRegCloseKey(ServiceNameKey);
|
|
}
|
|
} // endif SERVICE_WIN32_OWN_PROCESS
|
|
|
|
LocalFree(ServiceRecord->Dependencies);
|
|
|
|
//
|
|
// Free up the DisplayName space.
|
|
//
|
|
if (ServiceRecord->DisplayName != ServiceRecord->ServiceName)
|
|
{
|
|
LocalFree(ServiceRecord->DisplayName);
|
|
}
|
|
|
|
ScDeleteGroupMembership(ServiceRecord);
|
|
ScDeleteRegistryGroupPointer(ServiceRecord);
|
|
|
|
ScDeleteStartDependencies(ServiceRecord);
|
|
ScDeleteStopDependencies(ServiceRecord);
|
|
|
|
if (ServiceRecord->ServiceSd != NULL)
|
|
{
|
|
RtlDeleteSecurityObject(&ServiceRecord->ServiceSd);
|
|
}
|
|
|
|
delete ServiceRecord->CrashRecord;
|
|
|
|
//*******************************
|
|
// Delete the registry node for
|
|
// This service.
|
|
//*******************************
|
|
DeleteServicePlugPlayRegKeys(ServiceRecord->ServiceName);
|
|
ScDeleteRegServiceEntry(ServiceRecord->ServiceName);
|
|
|
|
REMOVE_FROM_LIST(ServiceRecord);
|
|
|
|
ScFreeServiceRecord(ServiceRecord);
|
|
|
|
} // End If service can be deleted.
|
|
|
|
} // End for each element in the list.
|
|
|
|
//
|
|
// The deferred list is no longer needed free it.
|
|
//
|
|
LocalFree(ServiceRecordPtr);
|
|
ServiceRecordPtr = NULL;
|
|
TotalElements = 0;
|
|
NumElements = 0;
|
|
|
|
SC_LOG0(LOCKS, "Returning from ScProcessDeferredList\n");
|
|
}
|
|
|
|
/****************************************************************************/
|
|
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:
|
|
|
|
|
|
--*/
|
|
{
|
|
FOR_SERVICES_THAT(serviceRecord, serviceRecord->ResumeNum > ResumeIndex)
|
|
{
|
|
*ServiceRecordPtr = serviceRecord;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
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.
|
|
|
|
NOTE: If this function is called, it is to find a shareable Image Record
|
|
of the given name.
|
|
|
|
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.
|
|
|
|
--*/
|
|
{
|
|
if (ImageName == NULL)
|
|
{
|
|
SC_LOG(TRACE,"GetNamedImageRecord: Name was NULL\n",0);
|
|
return (FALSE);
|
|
}
|
|
|
|
SC_ASSERT(ScServiceRecordLock.Have());
|
|
|
|
//
|
|
// Check the database of running images
|
|
//
|
|
for (PIMAGE_RECORD imageRecord = ImageDatabase.Next;
|
|
imageRecord != NULL;
|
|
imageRecord = imageRecord->Next)
|
|
{
|
|
//
|
|
// We need a shareable Image Record, so check the ImageFlags
|
|
//
|
|
if (ScImagePathsMatch(imageRecord->ImageName, ImageName) &&
|
|
(imageRecord->ImageFlags & CANSHARE_FLAG))
|
|
{
|
|
*ImageRecordPtr = imageRecord;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
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.
|
|
|
|
--*/
|
|
{
|
|
if (ServiceName == NULL)
|
|
{
|
|
SC_LOG0(TRACE,"GetNamedServiceRecord: Name was NULL\n");
|
|
return ERROR_INVALID_NAME;
|
|
}
|
|
|
|
//
|
|
// Check the database of running services
|
|
//
|
|
FOR_SERVICES_THAT(serviceRecord,
|
|
_wcsicmp(serviceRecord->ServiceName, ServiceName)== 0)
|
|
{
|
|
*ServiceRecordPtr = serviceRecord;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
return ERROR_SERVICE_DOES_NOT_EXIST;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
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.
|
|
|
|
--*/
|
|
{
|
|
if (ServiceDisplayName == NULL)
|
|
{
|
|
SC_LOG0(TRACE,"GetDisplayNamedServiceRecord: Name was NULL\n");
|
|
return ERROR_INVALID_NAME;
|
|
}
|
|
|
|
//
|
|
// Check the database of running services
|
|
//
|
|
SC_ASSERT(ScServiceRecordLock.Have());
|
|
FOR_SERVICES_THAT(serviceRecord,
|
|
_wcsicmp(serviceRecord->DisplayName, ServiceDisplayName)== 0)
|
|
{
|
|
*ServiceRecordPtr = serviceRecord;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
return ERROR_SERVICE_DOES_NOT_EXIST;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
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;
|
|
|
|
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.
|
|
//
|
|
|
|
ScServiceRecordLock.Initialize(" R", "ServiceRecord");
|
|
ScServiceListLock.Initialize(" L ", "ServiceList");
|
|
|
|
|
|
//
|
|
// Initialize the group list lock used for protecting the
|
|
// OrderGroupList and StandaloneGroupList
|
|
//
|
|
ScGroupListLock.Initialize("G ", "GroupList");
|
|
|
|
//
|
|
// 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
|
|
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.
|
|
|
|
--*/
|
|
{
|
|
PIMAGE_RECORD imageRecord;
|
|
|
|
{
|
|
//
|
|
// Get exclusive use of database so that it can be modified.
|
|
//
|
|
// If the service record's update flag is set, we may have to
|
|
// modify the group list (within ScDeactivateServiceRecord),
|
|
// so lock the group list too.
|
|
//
|
|
|
|
CGroupListExclusiveLock GLock;
|
|
CServiceListSharedLock LLock;
|
|
CServiceRecordExclusiveLock RLock;
|
|
|
|
//
|
|
// Find the image record that has this ProcessHandle.
|
|
//
|
|
|
|
for (imageRecord = ImageDatabase.Next;
|
|
imageRecord != NULL;
|
|
imageRecord = imageRecord->Next)
|
|
{
|
|
if (ProcessHandle == imageRecord->ProcessHandle)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (imageRecord == NULL)
|
|
{
|
|
SC_LOG(ERROR, "ScProcessCleanup: No image record has handle %#lx!\n",
|
|
ProcessHandle);
|
|
return;
|
|
}
|
|
|
|
SC_LOG2(ERROR, "Service process %ld (%ws) died\n", imageRecord->Pid,
|
|
imageRecord->ImageName);
|
|
|
|
//
|
|
// Deregister the wait. Note that this must be done
|
|
// even if the WT_EXECUTEONLYONCE flag was specified)
|
|
//
|
|
|
|
if (imageRecord->ObjectWaitHandle != NULL)
|
|
{
|
|
NTSTATUS ntStatus = RtlDeregisterWait(imageRecord->ObjectWaitHandle);
|
|
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
SC_LOG1(ERROR,
|
|
"ScProcessCleanup: RtlDeregisterWait FAILED %#x\n",
|
|
ntStatus);
|
|
}
|
|
}
|
|
|
|
DWORD serviceCount = imageRecord->ServiceCount;
|
|
|
|
//
|
|
// 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_SERVICES_THAT(serviceRecord,
|
|
(serviceRecord->ImageRecord == imageRecord)
|
|
&&
|
|
(serviceRecord->ServiceStatus.dwCurrentState != SERVICE_STOPPED))
|
|
{
|
|
SC_LOG2(ERROR, "Dead process contained %ws service in state %#lx\n",
|
|
serviceRecord->ServiceName,
|
|
serviceRecord->ServiceStatus.dwCurrentState);
|
|
|
|
//
|
|
// 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)
|
|
{
|
|
if (serviceRecord->ServiceStatus.dwControlsAccepted
|
|
&
|
|
(SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_HARDWAREPROFILECHANGE))
|
|
{
|
|
//
|
|
// Tell PnP not to send controls to this service any more
|
|
//
|
|
RegisterServiceNotification((SERVICE_STATUS_HANDLE)serviceRecord,
|
|
serviceRecord->ServiceName,
|
|
0,
|
|
TRUE);
|
|
}
|
|
|
|
//
|
|
// Increment the service's crash count and perform the configured
|
|
// recovery action. Don't bother if the system is shutting down.
|
|
//
|
|
|
|
ScQueueRecoveryAction(serviceRecord);
|
|
}
|
|
|
|
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);
|
|
|
|
//
|
|
// Remove the ImageRecord from the list and delete it
|
|
//
|
|
REMOVE_FROM_LIST(imageRecord);
|
|
}
|
|
|
|
ScDeleteImageRecord(imageRecord);
|
|
|
|
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
|
|
|
|
--*/
|
|
{
|
|
HKEY ServiceNameKey;
|
|
LPWSTR AccountName;
|
|
|
|
FOR_SERVICES_THAT(serviceRecord, DELETE_FLAG_IS_SET(serviceRecord))
|
|
{
|
|
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
|
|
&&
|
|
AccountName != NULL)
|
|
{
|
|
if (_wcsicmp(AccountName, SC_LOCAL_SYSTEM_USER_NAME) != 0)
|
|
{
|
|
ScRemoveAccount(serviceRecord->ServiceName);
|
|
}
|
|
|
|
LocalFree(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(serviceRecord->DisplayName);
|
|
}
|
|
|
|
//
|
|
// Remove the service record from the database
|
|
//
|
|
LPSERVICE_RECORD saveRecord = serviceRecord->Prev;
|
|
REMOVE_FROM_LIST(serviceRecord);
|
|
|
|
ScFreeServiceRecord(serviceRecord);
|
|
|
|
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.
|
|
|
|
This function deactivates the service record (ScDeactivateServiceRecord)
|
|
and checks to see if the ServiceCount in the image record 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.
|
|
|
|
--*/
|
|
{
|
|
DWORD serviceCount = 0;
|
|
LPIMAGE_RECORD ImageRecord;
|
|
|
|
{
|
|
//
|
|
// Get exclusive use of database so that it can be modified.
|
|
// If the service record's update flag is set, we may have to
|
|
// modify the group list, so lock the group list too.
|
|
//
|
|
CGroupListExclusiveLock GLock;
|
|
CServiceListSharedLock LLock; // (needed if update flag set and service has dependencies)
|
|
CServiceRecordExclusiveLock RLock;
|
|
|
|
ImageRecord = ServiceRecord->ImageRecord;
|
|
|
|
//
|
|
// ImageRecord may be NULL if it had been cleaned up earlier
|
|
//
|
|
if (ImageRecord != NULL)
|
|
{
|
|
//
|
|
// Deactivate the service record.
|
|
//
|
|
serviceCount = ScDeactivateServiceRecord(ServiceRecord);
|
|
|
|
//
|
|
// Do as little as possible while holding the locks.
|
|
// Otherwise, we can cause a deadlock or a bottleneck
|
|
// on system shutdown due to contention for the
|
|
// exclusive locks
|
|
//
|
|
|
|
if (serviceCount == 0)
|
|
{
|
|
//
|
|
// Remove the Image record from linked list.
|
|
//
|
|
REMOVE_FROM_LIST(ImageRecord);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Done with modifications - now allow other threads database access.
|
|
//
|
|
|
|
if (ImageRecord != NULL && 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);
|
|
}
|
|
|
|
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 image record has already been removed
|
|
from the list before it is called via the REMOVE_FROM_LIST macro. This
|
|
is to allow us to call ScDeleteImageRecord without holding any exclusive
|
|
locks
|
|
|
|
--*/
|
|
{
|
|
HANDLE status; // return status from LocalFree
|
|
|
|
SC_ASSERT( ImageRecord != NULL );
|
|
|
|
//
|
|
// 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(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).
|
|
If the service's update flag is set (its configuration was changed
|
|
while it was running), the exclusive group list lock must also have
|
|
been obtained.
|
|
|
|
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);
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
//
|
|
// 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))
|
|
{
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
status = ScReadConfigFromReg(
|
|
ServiceRecord,
|
|
&dwServiceType,
|
|
&dwStartType,
|
|
&dwErrorControl,
|
|
&dwTagId,
|
|
NULL, // Don't need dependencies -- they're updated dynamically
|
|
&lpLoadOrderGroup,
|
|
NULL); // Don't need display name -- it's updated dynamically
|
|
|
|
if (status == NO_ERROR)
|
|
{
|
|
|
|
//
|
|
// Dependencies are NULL since they're updated dynamically
|
|
//
|
|
status = ScUpdateServiceRecordConfig(
|
|
ServiceRecord,
|
|
dwServiceType,
|
|
dwStartType,
|
|
dwErrorControl,
|
|
lpLoadOrderGroup,
|
|
NULL);
|
|
}
|
|
if (status != NO_ERROR)
|
|
{
|
|
SC_LOG1(ERROR,"ScDeactivateServiceRecord:Attempt to update "
|
|
"configuration for stopped service failed\n",status);
|
|
//
|
|
// ERROR_LOG ErrorLog
|
|
//
|
|
}
|
|
|
|
LocalFree(lpLoadOrderGroup);
|
|
LocalFree(lpDependencies);
|
|
LocalFree(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;
|
|
|
|
//
|
|
// Check vs. NULL in case the register failed or the work item
|
|
// was already deregistered in RSetServiceStatus
|
|
//
|
|
if (ImageRecord->ObjectWaitHandle != NULL) {
|
|
|
|
status = RtlDeregisterWait(ImageRecord->ObjectWaitHandle);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
ImageRecord->ObjectWaitHandle = NULL;
|
|
}
|
|
else {
|
|
|
|
SC_LOG1(ERROR,
|
|
"ScTerminateServiceProcess: RtlDeregisterWait failed 0x%x\n",
|
|
status);
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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);
|
|
|
|
//
|
|
// Stop the service's control dispatcher
|
|
//
|
|
|
|
status = ScSendControl(
|
|
L"", // no service name.
|
|
L"", // no display name.
|
|
ImageRecord->PipeHandle, // PipeHandle
|
|
SERVICE_STOP, // Opcode
|
|
NULL, // CmdArgs (pointer to vectors).
|
|
0L, // NumArgs
|
|
NULL); // Ignore handler return value
|
|
|
|
|
|
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_LOG3(ERROR,"TerminateServiceProcess: Process %#lx (%ws) did not exit because of timeout: %#ld\n",
|
|
ImageRecord->Pid, ImageRecord->ImageName, WAIT_TIMEOUT);
|
|
|
|
//
|
|
// 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_LOG2(ERROR,"TerminateServiceProcess: Couldn't kill process %#lx because of timeout: %#ld\n",
|
|
ImageRecord->Pid, WAIT_TIMEOUT);
|
|
}
|
|
returnStatus = NO_ERROR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// ScSendControl failed -- this can occur if the service calls ExitProcess
|
|
// while handling SERVICE_CONTROL_STOP or SERVICE_CONTROL_SHUTDOWN since
|
|
// the pipe for the image record is now broken (ERROR_BROKEN_PIPE).
|
|
//
|
|
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_LOG2(ERROR,"TerminateServiceProcess: Couldn't kill process because of timeout: %ld\n",
|
|
0, WAIT_TIMEOUT);
|
|
}
|
|
returnStatus = NO_ERROR;
|
|
|
|
}
|
|
SC_LOG(TRACE,"TerminateServiceProcess, Done terminating Process!\n",0);
|
|
return(returnStatus);
|
|
}
|
|
|
|
|
|
VOID
|
|
ScDeferredListWorkItem(
|
|
IN PVOID pContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function acquires all the database locks, and allows
|
|
the group list lock routine to process the deferred list.
|
|
|
|
--*/
|
|
{
|
|
ScDeferredList.Process();
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
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?
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
LocalFree(OldDependencies);
|
|
LocalFree(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);
|
|
}
|