mirror of https://github.com/lianthony/NT4.0
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.
1295 lines
33 KiB
1295 lines
33 KiB
/*++
|
|
|
|
Copyright (c) 1990 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
svcctrl.c
|
|
|
|
Abstract:
|
|
|
|
This is the main routine for the NT LAN Manager Service Controller.
|
|
|
|
To use this as a template for another service, simply replace the string
|
|
"svcctl" with the name of the new interface.
|
|
|
|
Author:
|
|
|
|
Dan Lafferty (danl) 20-Mar-1991
|
|
|
|
Environment:
|
|
|
|
User Mode - Win32
|
|
|
|
Revision History:
|
|
|
|
11-Jun-1996 AnirudhS
|
|
Don't popup messages during setup. The most common cause of popups
|
|
during upgrade is that a service runs in a domain account, and hence
|
|
has a dependency on netlogon, which is disabled during upgrade.
|
|
26-Jun-1995 AnirudhS
|
|
Added callouts to service object class code (ScNotifyServiceObject).
|
|
20-Oct-1993 Danl
|
|
Added Globals for ScConnectedToSecProc and ScGlobalNetLogonName.
|
|
28-Oct-1992 Danl
|
|
Removed ParseArgs and the NT event. Added Windows event for
|
|
synchronizing service controller with the OpenSCManager client side.
|
|
OpenScManager will now wait until the service controller event is
|
|
set.
|
|
20-Mar-1991 danl
|
|
created
|
|
|
|
--*/
|
|
//
|
|
// INCLUDES
|
|
//
|
|
#include <stdio.h> // printf
|
|
|
|
#include <nt.h>
|
|
#include <ntrtl.h> // DbgPrint prototype
|
|
#include <rpc.h> // DataTypes and runtime APIs
|
|
#include <svcctl.h> // generated by the MIDL complier
|
|
#include <nturtl.h> // needed for winbase.h
|
|
#include <windows.h> // LocalAlloc
|
|
#include <winuserp.h> // RegisterServicesProcess
|
|
|
|
#include <lmcons.h> // needed by lmalert.h
|
|
#include <lmalert.h> // NetAlertRaiseEx definitions
|
|
#include <alertmsg.h> // ALERT_SC_IsLastKnownGood
|
|
#ifdef _CAIRO_
|
|
#include <wtypes.h> // HRESULT
|
|
#include <scmso.h> // ScmCallSvcObject
|
|
#endif
|
|
|
|
#include <tstr.h> // Unicode string macros
|
|
|
|
#include <ntrpcp.h> // Rpcp... function prototypes
|
|
|
|
#include <scdebug.h> // SC_LOG
|
|
#include <sclib.h> // SC_INTERNAL_START_EVENT
|
|
#include <dataman.h>
|
|
#include "scopen.h" // SC_HANDLE_STRUCT needed by scsec.h
|
|
#include "scsec.h" // Security object functions
|
|
#include <svcslib.h> // SvcInitThreadManager
|
|
#include "scconfig.h" // ScInitSecurityProcess
|
|
#include "svcctrl.h" // InitStartImage
|
|
#include "depend.h" // ScAutoStartServices
|
|
#include "bootcfg.h" // ScCheckLastKnownGood()
|
|
#include "account.h" // ScInitServiceAccount
|
|
#include "info.h" // ScGetBootAndSystemDriverState
|
|
#include "control.h" // ScShutdownAllServices
|
|
#include "lockapi.h" // ScLockDatabase
|
|
#include "scbsm.h" // ScInitBSM
|
|
#include <svcs.h> // SVCS_RPC_PIPE
|
|
#include <svcslib.h> // SvcAddWorkItem
|
|
|
|
//
|
|
// The following turns on code that captures time info & displays it.
|
|
// To be used for performance analysis.
|
|
//
|
|
//#define TIMING_TEST 1
|
|
|
|
//
|
|
// Defines
|
|
//
|
|
|
|
#define SVCCTRL_SHUTDOWN_LEVEL 480
|
|
#define SCREG_BASE_PRIORITY 9
|
|
|
|
typedef DWORD (WINAPI *SETSBPROC)();
|
|
|
|
//===========================
|
|
// Globals
|
|
//===========================
|
|
#ifdef SC_DEBUG
|
|
DWORD SvcctrlDebugLevel = DEBUG_ERROR | DEBUG_TRACE;
|
|
#else
|
|
DWORD SvcctrlDebugLevel = DEBUG_ERROR;
|
|
#endif
|
|
|
|
DWORD ScShutdownInProgress = FALSE;
|
|
|
|
//
|
|
// For determining if the service controller is still in its
|
|
// initialization code.
|
|
//
|
|
BOOL ScStillInitializing = TRUE;
|
|
|
|
//
|
|
// For the service controller to put up a popup to notify the first
|
|
// logged on user if any boot, system, or auto start services failed
|
|
// to start.
|
|
//
|
|
BOOL ScPopupStartFail = FALSE;
|
|
|
|
#ifndef _CAIRO_
|
|
//
|
|
// Flag indicating whether or not NetLogon has been created, and we
|
|
// have successfully connected to the Security Process .
|
|
// If it hasn't then we need to look for it when it is created so that
|
|
// we can synchronize with lsass appropriately.
|
|
//
|
|
BOOL ScConnectedToSecProc = FALSE;
|
|
#endif // _CAIRO_
|
|
|
|
//
|
|
// Linked list of names of boot or system start drivers which failed
|
|
// to load. This list is logged to the eventlog.
|
|
//
|
|
LPFAILED_DRIVER ScFailedDrivers = NULL;
|
|
DWORD ScTotalSizeFailedDrivers = 0;
|
|
|
|
#ifndef _CAIRO_
|
|
//
|
|
// The NetLogon Service is special because it resides in the security
|
|
// process which is started by winlogon, not by the service controller.
|
|
// We have to synchronize with it and connect the control pipe during
|
|
// our init or when CreateService is called for Netlogon.
|
|
//
|
|
LPWSTR ScGlobalNetLogonName = L"NETLOGON";
|
|
#endif // _CAIRO_
|
|
|
|
//
|
|
// ScGlobalThisExePath gets initialized to the full path of where this
|
|
// executable image is to be. This is later used to create an image
|
|
// record for services that run in the context of this process.
|
|
//
|
|
LPWSTR ScThisExeExpandPath = L"%SystemRoot%\\system32\\services.exe";
|
|
LPWSTR ScGlobalThisExePath = NULL;
|
|
|
|
//
|
|
// ScGlobalProductType contains the product type for this machine.
|
|
// Possiblilties are NtProductWinNt, NtProductLanManNt, NtProductServer.
|
|
//
|
|
NT_PRODUCT_TYPE ScGlobalProductType;
|
|
|
|
//=================================
|
|
// prototypes
|
|
//=================================
|
|
BOOL
|
|
ScGetStartEvent(
|
|
LPHANDLE pScStartEvent
|
|
);
|
|
|
|
VOID
|
|
ScPopupThread(
|
|
DWORD StartFailFlag
|
|
);
|
|
|
|
VOID
|
|
ScDestroyFailedDriverList(
|
|
VOID
|
|
);
|
|
|
|
DWORD
|
|
ScMakeFailedDriversOneString(
|
|
LPWSTR *DriverList
|
|
);
|
|
|
|
DWORD
|
|
ScGroupChangeIsSignaled(
|
|
PVOID pContext,
|
|
DWORD dwWaitStatus
|
|
);
|
|
|
|
|
|
VOID
|
|
SvcctrlMain (
|
|
int argc,
|
|
PUCHAR argv[]
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the main routine for the Service Controller. It sets up
|
|
the RPC interface.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
Note:
|
|
|
|
|
|
--*/
|
|
{
|
|
RPC_STATUS status;
|
|
NTSTATUS ntStatus;
|
|
HANDLE ScStartEvent;
|
|
HANDLE ThreadHandle;
|
|
DWORD ThreadId;
|
|
SC_RPC_LOCK Lock=NULL;
|
|
KPRIORITY NewBasePriority = SCREG_BASE_PRIORITY;
|
|
HANDLE hChangeEvent=NULL;
|
|
HANDLE hChangeItemHandle=NULL;
|
|
|
|
//
|
|
// Save bitwise flags to indicate the amount of initialization
|
|
// work done so that if we hit an error along the way, the
|
|
// appropriate amount of shutdown can occur.
|
|
//
|
|
DWORD ScInitState = 0;
|
|
|
|
#ifdef TIMING_TEST
|
|
DWORD TickCount1;
|
|
DWORD TickCount2;
|
|
DWORD TickCount3;
|
|
|
|
TickCount1 = GetTickCount();
|
|
#endif // TIMING_TEST
|
|
|
|
#if DBG
|
|
//
|
|
// Read the debug message level from the registry.
|
|
//
|
|
{
|
|
HKEY WinlogonKey;
|
|
|
|
if (RegOpenKeyEx(
|
|
HKEY_LOCAL_MACHINE,
|
|
L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon",
|
|
0,
|
|
KEY_QUERY_VALUE,
|
|
&WinlogonKey) == ERROR_SUCCESS)
|
|
{
|
|
DWORD ValueType;
|
|
DWORD DebugLevel;
|
|
DWORD cbData = sizeof(DebugLevel);
|
|
|
|
if (RegQueryValueEx(
|
|
WinlogonKey,
|
|
L"ServiceControllerDebug",
|
|
0,
|
|
&ValueType,
|
|
(LPBYTE) &DebugLevel,
|
|
&cbData) == ERROR_SUCCESS
|
|
&&
|
|
ValueType == REG_DWORD)
|
|
{
|
|
SC_LOG(ERROR, "Debug level = %#lx\n", DebugLevel);
|
|
SvcctrlDebugLevel = DebugLevel;
|
|
}
|
|
|
|
RegCloseKey(WinlogonKey);
|
|
}
|
|
}
|
|
#endif // DBG
|
|
|
|
|
|
//
|
|
// Create a string containing the pathname for this executable image.
|
|
//
|
|
{
|
|
DWORD NumChars = 0;
|
|
DWORD CharsReturned = 0;
|
|
WCHAR Temp[1];
|
|
LPWSTR TempValue = NULL;
|
|
|
|
NumChars = ExpandEnvironmentStringsW(ScThisExeExpandPath,Temp,1);
|
|
if (NumChars > 1) {
|
|
ScGlobalThisExePath = (LPWSTR)LocalAlloc(
|
|
LPTR,
|
|
NumChars * sizeof(WCHAR));
|
|
if (ScGlobalThisExePath == NULL) {
|
|
SC_LOG0(ERROR,"Couldn't allocate for ThisExePath\n");
|
|
goto CleanExit;
|
|
}
|
|
CharsReturned = ExpandEnvironmentStringsW(
|
|
ScThisExeExpandPath,
|
|
ScGlobalThisExePath,
|
|
NumChars);
|
|
if (CharsReturned > NumChars) {
|
|
SC_LOG0(ERROR,"Couldn't expand ThisExePath\n");
|
|
goto CleanExit;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Create well-known SIDs
|
|
//
|
|
if (! NT_SUCCESS(ntStatus = ScCreateWellKnownSids())) {
|
|
SC_LOG1(ERROR, "ScCreateWellKnownSids failed: %08lx\n", ntStatus);
|
|
goto CleanExit;
|
|
}
|
|
ScInitState |= WELL_KNOWN_SIDS_CREATED;
|
|
|
|
//
|
|
// Create the event that the OpenSCManager will use to wait on the
|
|
// service controller with.
|
|
//
|
|
if (!ScGetStartEvent(&ScStartEvent)) {
|
|
SC_LOG0(ERROR,"SvcctrlMain: ScGetStartEvent Failed\n");
|
|
goto CleanExit;
|
|
}
|
|
|
|
ScInitState |= SC_NAMED_EVENT_CREATED;
|
|
|
|
//
|
|
// Create security descriptor for SC Manager object to protect
|
|
// the SC Manager databases
|
|
//
|
|
if (ScCreateScManagerObject() != NO_ERROR) {
|
|
SC_LOG0(ERROR, "ScCreateScManagerObject failed\n");
|
|
goto CleanExit;
|
|
}
|
|
ScInitState |= SC_MANAGER_OBJECT_CREATED;
|
|
|
|
//
|
|
// Get the ProductType.
|
|
//
|
|
if (!RtlGetNtProductType(&ScGlobalProductType)) {
|
|
SC_LOG0(ERROR, "GetNtProductType failed\n");
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Check the Boot Configuration and assure that the LastKnownGood
|
|
// ControlSet is safe, and pointers are correct.
|
|
// This function initializes the ScGlobalLastKnownGood flag.
|
|
//
|
|
|
|
if (!ScCheckLastKnownGood()) {
|
|
SC_LOG0(ERROR, "ScCheckLastKnownGood failed\n");
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Initialize data structures required to remove a service account.
|
|
// They will be cleaned up by ScEndServiceAccount.
|
|
//
|
|
// NOTE: ScGetComputerNameAndMutex must be called before call to
|
|
// ScInitDatabase because ScInitDatabase may delete a service
|
|
// entry that was marked for delete from a previous boot.
|
|
//
|
|
if (! ScGetComputerNameAndMutex()) {
|
|
SC_LOG0(ERROR, "ScGetComputerName failed\n");
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Read installed services into memory
|
|
//
|
|
if (! ScInitDatabase()) {
|
|
SC_LOG0(ERROR, "ScInitDatabase failed\n");
|
|
goto CleanExit;
|
|
}
|
|
ScInitState |= SC_DATABASE_INITIALIZED;
|
|
|
|
#ifndef _CAIRO_
|
|
//
|
|
// Initialize the Security Process. This should create a single
|
|
// image record.
|
|
// NOTE: We don't have any code in CleanExit to free storage created by
|
|
// this function.
|
|
//
|
|
if (!ScInitSecurityProcess()) {
|
|
//
|
|
// If this fails, that means we will never be able to start
|
|
// netlogon. It may also mean that the Security Process may not
|
|
// be up. However, we want to keep running and allow the system
|
|
// to limp along as best as it can under the circumstances.
|
|
//
|
|
SC_LOG0(ERROR, "ScInitSecurityProcess failed\n");
|
|
}
|
|
#endif // _CAIRO_
|
|
|
|
//
|
|
// Initialize accounts functionality.
|
|
//
|
|
if (! ScInitServiceAccount()) {
|
|
SC_LOG0(ERROR, "ScInitServiceAccount failed\n");
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Create critical sections
|
|
//
|
|
ScInitStartImage();
|
|
ScInitTransactNamedPipe();
|
|
ScInitState |= CRITICAL_SECTIONS_CREATED;
|
|
|
|
//
|
|
// Initialize the Service Process Watcher. If this fails, it just
|
|
// means that we will not be able to clean up when processes
|
|
// terminate unexpectedly.
|
|
//
|
|
if (SvcInitThreadManager()) {
|
|
ScInitState |= WATCHER_INITIALIZED;
|
|
}
|
|
else {
|
|
SC_LOG0(ERROR, "SvcInitThreadManager failed\n");
|
|
}
|
|
|
|
//
|
|
// Create an event used for registry change notify on the
|
|
// ServiceGroupOrder key
|
|
//
|
|
hChangeEvent = CreateEvent(
|
|
NULL, // Event Attributes
|
|
TRUE, // ManualReset
|
|
FALSE, // Initial State (not-signaled)
|
|
NULL); // Name
|
|
|
|
if (hChangeEvent == NULL) {
|
|
|
|
SC_LOG1(ERROR,
|
|
"SvcctrlMain:CreateEvent for NotifyChange failed %ld\n",
|
|
GetLastError());
|
|
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Add the change notify event handle to the object watcher work list.
|
|
// NOTE: This function will wait until the Watcher Thread is up and
|
|
// running.
|
|
//
|
|
|
|
hChangeItemHandle = SvcAddWorkItem(
|
|
hChangeEvent,
|
|
ScGroupChangeIsSignaled,
|
|
hChangeEvent,
|
|
SVC_QUEUE_WORK_ITEM,
|
|
INFINITE,
|
|
NULL);
|
|
|
|
if (hChangeItemHandle == NULL) {
|
|
SC_LOG1(ERROR,"SvcctrlMain: SvcAddWorkItem failed %d\n",GetLastError());
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Perform initialization related to network drive arrival broadcasts.
|
|
// (This addes another work item to the object watcher work list.)
|
|
//
|
|
ScInitBSM();
|
|
|
|
//
|
|
// Get the latest state of drivers started up by boot and system
|
|
// init.
|
|
//
|
|
ScGetBootAndSystemDriverState();
|
|
|
|
//
|
|
// Create semaphores needed for handling start dependencies
|
|
//
|
|
if (! ScInitAutoStart()) {
|
|
SC_LOG0(ERROR, "ScInitAutoStart failed\n");
|
|
goto CleanExit;
|
|
}
|
|
ScInitState |= AUTO_START_INITIALIZED;
|
|
|
|
//
|
|
// Register this process with User32. This tells User32 to use the
|
|
// value from HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control
|
|
// \WaitToKillServiceTimeout (if it exists), rather than
|
|
// HKEY_CURRENT_USER\Control Panel\Desktop\WaitToKillAppTimeout,
|
|
// to decide how long to wait before killing us on shutdown.
|
|
//
|
|
if (! RegisterServicesProcess(GetCurrentProcessId())) {
|
|
SC_LOG0(ERROR, "RegisterServicesProcess failed\n");
|
|
}
|
|
|
|
//
|
|
// Lock the database until autostart is complete
|
|
//
|
|
status = ScLockDatabase(TRUE, SERVICES_ACTIVE_DATABASEW, &Lock);
|
|
if (status != NO_ERROR) {
|
|
SC_LOG1(ERROR, "ScLockDatabase failed during init %d\n",status);
|
|
goto CleanExit;
|
|
}
|
|
|
|
//
|
|
// Start the RPC server
|
|
//
|
|
SC_LOG0(TRACE, "Getting ready to start RPC server\n");
|
|
|
|
status = RpcpStartRpcServer(
|
|
SVCS_RPC_PIPE,
|
|
svcctl_ServerIfHandle);
|
|
|
|
if ( !NT_SUCCESS(status) ) {
|
|
//
|
|
// BUGBUG: We should write to the error log if we can't start up.
|
|
//
|
|
SC_LOG1(ERROR, "RpcpStartRpcServer: %lx\n",status);
|
|
goto CleanExit;
|
|
}
|
|
ScInitState |= RPC_SERVER_STARTED;
|
|
|
|
//
|
|
// Signal the event that indicates that we are completely started.
|
|
//
|
|
if (!SetEvent(ScStartEvent)) {
|
|
SC_LOG1(ERROR, "Unable to set StartEvent: %d\n", GetLastError());
|
|
}
|
|
|
|
SC_LOG0(ERROR,"Service Controller successfully initialized\n");
|
|
|
|
//
|
|
// Set up for proper shutdown.
|
|
//
|
|
|
|
if (!SetConsoleCtrlHandler(ScShutdownNotificationRoutine, TRUE)) {
|
|
SC_LOG1(ERROR, "SetConsoleCtrlHandler call failed %d\n",GetLastError());
|
|
}
|
|
|
|
if (!SetProcessShutdownParameters(SVCCTRL_SHUTDOWN_LEVEL,SHUTDOWN_NORETRY)) {
|
|
SC_LOG1(ERROR, "SetProcessShutdownParameters call failed %d\n",
|
|
GetLastError());
|
|
}
|
|
SC_LOG0(TRACE,"** ** Service Controller can now accept shutdown system request\n");
|
|
|
|
//
|
|
// Auto-start services
|
|
//
|
|
ScAutoStartServices();
|
|
|
|
//
|
|
// Log event if any boot/system start drivers failed.
|
|
//
|
|
if (ScFailedDrivers != NULL) {
|
|
|
|
LPWSTR DriverList;
|
|
LPWSTR SubStrings[1];
|
|
|
|
|
|
(void) ScMakeFailedDriversOneString(&DriverList);
|
|
SubStrings[0] = DriverList;
|
|
|
|
ScLogEvent(
|
|
EVENT_BOOT_SYSTEM_DRIVERS_FAILED,
|
|
1,
|
|
SubStrings
|
|
);
|
|
|
|
(void) LocalFree((HLOCAL) DriverList);
|
|
|
|
ScDestroyFailedDriverList();
|
|
}
|
|
|
|
//
|
|
// Spin a thread to put up popup if a service specified to start
|
|
// automatically at boot has failed to start, or we are running the
|
|
// last-known-good configuration.
|
|
// Don't popup any messages during setup/upgrade. (The most common
|
|
// cause of messages during upgrade is dependence on netlogon, which
|
|
// is disabled.)
|
|
//
|
|
if ((ScPopupStartFail || (ScGlobalLastKnownGood & REVERTED_TO_LKG))
|
|
&&
|
|
(! SetupInProgress(NULL))) {
|
|
|
|
//
|
|
// Suppress the popups if NoPopupsOnBoot is indicated in the registry.
|
|
//
|
|
DWORD PopupStatus;
|
|
BOOLEAN bPopups = TRUE; // FALSE means suppress popups on boot
|
|
HKEY WindowsKey=NULL;
|
|
|
|
PopupStatus = ScRegOpenKeyExW(
|
|
HKEY_LOCAL_MACHINE,
|
|
CONTROL_WINDOWS_KEY_W,
|
|
REG_OPTION_NON_VOLATILE, // options
|
|
KEY_READ, // desired access
|
|
&WindowsKey
|
|
);
|
|
|
|
if (PopupStatus == ERROR_SUCCESS) {
|
|
|
|
DWORD Type;
|
|
DWORD Data;
|
|
DWORD cbData = sizeof(Data);
|
|
|
|
PopupStatus = ScRegQueryValueExW(
|
|
WindowsKey,
|
|
NOBOOTPOPUPS_VALUENAME_W,
|
|
NULL,
|
|
&Type,
|
|
(PVOID) &Data,
|
|
&cbData
|
|
);
|
|
|
|
//
|
|
// Popups are suppressed if the NOBOOTPOPUPS_VALUENAME_W value is
|
|
// present, is a REG_DWORD and is non-zero.
|
|
//
|
|
if (PopupStatus == ERROR_SUCCESS &&
|
|
Type == REG_DWORD &&
|
|
Data != 0) {
|
|
|
|
bPopups = FALSE;
|
|
}
|
|
|
|
ScRegCloseKey(WindowsKey);
|
|
}
|
|
|
|
|
|
if (bPopups) {
|
|
|
|
ThreadHandle = CreateThread(
|
|
NULL,
|
|
0L,
|
|
(LPTHREAD_START_ROUTINE) ScPopupThread,
|
|
(LPVOID) ScPopupStartFail,
|
|
0L,
|
|
&ThreadId
|
|
);
|
|
|
|
if (ThreadHandle == (HANDLE) NULL) {
|
|
SC_LOG(TRACE,"CreateThread ScPopupThread failed %lu\n",
|
|
GetLastError());
|
|
|
|
}
|
|
else {
|
|
(void) CloseHandle(ThreadHandle);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef _CAIRO_
|
|
//
|
|
// Pass the list of services to the service object class code, so
|
|
// it can create/delete service objects to match.
|
|
// (Should we open up the database for read access before we start
|
|
// doing this?)
|
|
//
|
|
ScNotifyServiceObject(SO_INIT, NULL, NULL, 0);
|
|
#endif // _CAIRO_
|
|
|
|
//
|
|
// Now we can allow database modifications from RPC callers.
|
|
//
|
|
ScUnlockDatabase(&Lock);
|
|
|
|
#ifdef TIMING_TEST
|
|
TickCount2 = GetTickCount();
|
|
#endif
|
|
|
|
//
|
|
// Now switch to high priority class
|
|
//
|
|
(void) NtSetInformationProcess(
|
|
NtCurrentProcess(),
|
|
ProcessBasePriority,
|
|
&NewBasePriority,
|
|
sizeof(NewBasePriority));
|
|
|
|
//
|
|
// If we get this far, then from our point of view, the boot is
|
|
// acceptable. We will now call the Accept Boot Program. This program
|
|
// will decide whether or not the boot was good (from the administrators)
|
|
// point of view.
|
|
// Our default program simply says the boot was good - thus causing
|
|
// LastKnownGood to be updated to the current boot.
|
|
//
|
|
ScRunAcceptBootPgm();
|
|
|
|
//
|
|
// Setup complete -
|
|
// This thread will become the service process watcher. Service
|
|
// process handles are stored in an array of waitble objects that
|
|
// the watcher thread waits on. When any ProcessHandle becomes
|
|
// signaled while in this array, this indicates that the process has
|
|
// terminated unexpectedly. The watcher thread then cleans up the
|
|
// service controller database.
|
|
//
|
|
ScStillInitializing = FALSE;
|
|
|
|
#ifdef TIMING_TEST
|
|
TickCount3 = GetTickCount();
|
|
DbgPrint("[SC_TIMING] Tick Count for autostart complete \t %d\n",TickCount2);
|
|
DbgPrint("[SC-TIMING] MSec for Autostart: \t%d\n",TickCount2-TickCount1);
|
|
DbgPrint("[SC-TIMING] MSec for LKG work: \t%d\n",TickCount3-TickCount2);
|
|
DbgPrint("[SC-TIMING] MSec to complete init:\t%d\n",TickCount3-TickCount1);
|
|
#endif
|
|
|
|
//
|
|
// BUGBUG: For proper cleanup when the the service controller shuts down,
|
|
// I should make the cleanup a seperate function, and make the various
|
|
// handles globals. Then register the function with SvcAddWorkItem().
|
|
//
|
|
ExitThread(NO_ERROR);
|
|
|
|
CleanExit:
|
|
|
|
ScStillInitializing = FALSE;
|
|
|
|
ScEndServiceAccount();
|
|
|
|
//
|
|
// Shut down the RPC server.
|
|
//
|
|
SC_LOG0(TRACE,"Shutting down the RPC interface for the Service Controller\n");
|
|
if (ScInitState & RPC_SERVER_STARTED) {
|
|
status = RpcpStopRpcServer (
|
|
svcctl_ServerIfHandle);
|
|
}
|
|
|
|
if (Lock != NULL) {
|
|
ScUnlockDatabase(&Lock);
|
|
}
|
|
|
|
if (ScInitState & AUTO_START_INITIALIZED) {
|
|
ScEndAutoStart();
|
|
}
|
|
|
|
if (ScInitState & CRITICAL_SECTIONS_CREATED) {
|
|
ScEndStartImage();
|
|
ScEndTransactNamedPipe();
|
|
}
|
|
|
|
if (ScInitState & SC_MANAGER_OBJECT_CREATED) {
|
|
ScDeleteScManagerObject();
|
|
}
|
|
|
|
if (ScInitState & SC_DATABASE_INITIALIZED) {
|
|
ScEndDatabase();
|
|
}
|
|
|
|
if (ScInitState & WELL_KNOWN_SIDS_CREATED) {
|
|
ScFreeWellKnownSids();
|
|
}
|
|
|
|
if (ScInitState & SC_NAMED_EVENT_CREATED) {
|
|
(void) CloseHandle(ScStartEvent);
|
|
}
|
|
|
|
if (ScInitState & WATCHER_INITIALIZED) {
|
|
SvcShutdownObjectWatcher();
|
|
}
|
|
if (hChangeEvent != NULL) {
|
|
LocalFree(hChangeEvent);
|
|
}
|
|
if (hChangeItemHandle != NULL) {
|
|
SvcRemoveWorkItem(hChangeItemHandle);
|
|
}
|
|
|
|
SC_LOG0(ERROR,"The Service Controller is Terminating.\n");
|
|
|
|
if (ScShutdownInProgress) {
|
|
ExitThread(0);
|
|
}
|
|
else {
|
|
ExitThread(0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
BOOL
|
|
ScShutdownNotificationRoutine(
|
|
DWORD dwCtrlType
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by the system when system shutdown is occuring.
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
|
|
--*/
|
|
{
|
|
if (dwCtrlType == CTRL_SHUTDOWN_EVENT) {
|
|
|
|
SC_LOG0(TRACE," ! SHUTDOWN ! - - In ScShutdownNotificationRoutine\n");
|
|
|
|
|
|
#ifndef SC_DEBUG
|
|
//
|
|
// First quiet all RPC interfaces
|
|
//
|
|
|
|
|
|
ScShutdownInProgress = TRUE;
|
|
#endif
|
|
|
|
//
|
|
// Then shut down all services
|
|
//
|
|
SC_LOG0(TRACE,"[Shutdown] Begin Service Shutdown\n");
|
|
ScShutdownAllServices();
|
|
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
VOID
|
|
ScLogEvent(
|
|
DWORD MessageId,
|
|
DWORD NumberOfSubStrings,
|
|
LPWSTR *ScSubStrings
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function logs an event.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
HANDLE LogHandle;
|
|
PSID UserSid = NULL;
|
|
|
|
|
|
LogHandle = RegisterEventSourceW (
|
|
NULL,
|
|
SCM_NAMEW
|
|
);
|
|
|
|
if (LogHandle == NULL) {
|
|
SC_LOG1(ERROR,"ScLogEvent: RegisterEventSource failed %d\n",
|
|
GetLastError());
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Log the event
|
|
//
|
|
if (!ReportEventW(
|
|
LogHandle,
|
|
EVENTLOG_ERROR_TYPE,
|
|
0, // event category
|
|
MessageId,
|
|
UserSid,
|
|
(WORD)NumberOfSubStrings,
|
|
0,
|
|
ScSubStrings,
|
|
(PVOID) NULL
|
|
)) {
|
|
SC_LOG1(ERROR,"ScLogEvent: RegisterEventSource failed %d\n",
|
|
GetLastError());
|
|
}
|
|
|
|
DeregisterEventSource(LogHandle);
|
|
}
|
|
|
|
BOOL
|
|
ScGetStartEvent(
|
|
LPHANDLE pScStartEvent
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function gets a handle to the SC_INTERNAL_START_EVENT that is
|
|
used to wait on the service controller when calling OpenSCManager.
|
|
|
|
Arguments:
|
|
|
|
pScStartEvent - This is a pointer to the location where the handle
|
|
to the event is to be placed.
|
|
|
|
Return Value:
|
|
|
|
TRUE - If a handle was obtained.
|
|
FALSE - If a handle was not obtained.
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
HANDLE ScStartEvent = NULL;
|
|
SECURITY_ATTRIBUTES SecurityAttributes;
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor=NULL;
|
|
|
|
//
|
|
// Initialize the status so that if we fail to create the security
|
|
// descriptor, we will still try to open the event.
|
|
//
|
|
status = ERROR_ALREADY_EXISTS;
|
|
|
|
//
|
|
// Create the event that the OpenSCManager will use to wait on the
|
|
// service controller with.
|
|
//
|
|
|
|
status = ScCreateStartEventSD(&SecurityDescriptor);
|
|
|
|
if (status == NO_ERROR) {
|
|
|
|
SecurityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
SecurityAttributes.bInheritHandle = FALSE;
|
|
SecurityAttributes.lpSecurityDescriptor = SecurityDescriptor;
|
|
|
|
ScStartEvent = CreateEventW(
|
|
&SecurityAttributes,
|
|
TRUE, // Must be manually reset
|
|
FALSE, // The event is initially not signalled
|
|
SC_INTERNAL_START_EVENT );
|
|
|
|
if (ScStartEvent == NULL) {
|
|
status = GetLastError();
|
|
}
|
|
LocalFree((HLOCAL) SecurityDescriptor);
|
|
}
|
|
else {
|
|
SC_LOG0(ERROR,"ScGetStartEvent: Couldn't allocate for SecurityDesc\n");
|
|
}
|
|
|
|
if (ScStartEvent == NULL){
|
|
|
|
//
|
|
// If the event already exists, some other process beat us to
|
|
// creating it. Just open it.
|
|
//
|
|
if ( status == ERROR_ALREADY_EXISTS ) {
|
|
ScStartEvent = OpenEvent(
|
|
GENERIC_WRITE,
|
|
FALSE,
|
|
SC_INTERNAL_START_EVENT );
|
|
}
|
|
|
|
if (ScStartEvent == NULL ) {
|
|
SC_LOG1(ERROR,"GetStartEvent: OpenEvent (StartEvent) Failed "
|
|
FORMAT_DWORD "\n", status);
|
|
return(FALSE);
|
|
}
|
|
}
|
|
*pScStartEvent = ScStartEvent;
|
|
return(TRUE);
|
|
}
|
|
|
|
VOID
|
|
ScPopupThread(
|
|
DWORD StartFailFlag
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function reports the state of the system that has just booted.
|
|
If we are running last-known-good:
|
|
1) Raise an admin alert
|
|
2) Put up a message box popup
|
|
|
|
If a service has failed to start (StartFailFlag is TRUE):
|
|
1) Put up a message box popup
|
|
|
|
The reason the StartFailFlag is a parameter is because its value
|
|
may change while we are in this thread. We only care about
|
|
its value at the time this thread is created.
|
|
|
|
Arguments:
|
|
|
|
StartFailFlag - Supplies a flag which indicates whether to put
|
|
up a popup due to services which failed to start.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
|
|
DWORD MessageSize;
|
|
HMODULE NetEventDll;
|
|
WCHAR Buffer[256];
|
|
WCHAR Title[256];
|
|
LPWSTR pTitle=NULL;
|
|
|
|
HMODULE NetApi32Dll = NULL;
|
|
SETSBPROC ScNetAlertRaiseEx = NULL;
|
|
KPRIORITY NewBasePriority = SCREG_BASE_PRIORITY;
|
|
|
|
|
|
if (ScGlobalLastKnownGood & REVERTED_TO_LKG) {
|
|
//
|
|
// Get address to API NetAlertRaiseEx to raise an Admin alert
|
|
//
|
|
NetApi32Dll = LoadLibraryW(L"netapi32.dll");
|
|
|
|
if (NetApi32Dll != NULL) {
|
|
ScNetAlertRaiseEx = (SETSBPROC) GetProcAddress(
|
|
NetApi32Dll,
|
|
"NetAlertRaiseEx"
|
|
);
|
|
|
|
if (ScNetAlertRaiseEx != (SETSBPROC) NULL) {
|
|
|
|
PADMIN_OTHER_INFO Admin;
|
|
|
|
|
|
//
|
|
// Raise an admin alert
|
|
//
|
|
Admin = (PADMIN_OTHER_INFO) Buffer;
|
|
Admin->alrtad_errcode = ALERT_SC_IsLastKnownGood;
|
|
Admin->alrtad_numstrings = 0;
|
|
|
|
(void) ScNetAlertRaiseEx(
|
|
ALERT_ADMIN_EVENT,
|
|
Buffer,
|
|
sizeof(ADMIN_OTHER_INFO),
|
|
SCM_NAMEW
|
|
);
|
|
|
|
}
|
|
|
|
(void) FreeLibrary(NetApi32Dll);
|
|
}
|
|
}
|
|
|
|
NetEventDll = LoadLibraryW(L"netevent.dll");
|
|
|
|
if (NetEventDll == NULL) {
|
|
return;
|
|
}
|
|
|
|
MessageSize = FormatMessageW(
|
|
FORMAT_MESSAGE_FROM_HMODULE,
|
|
(LPVOID) NetEventDll,
|
|
TITLE_SC_MESSAGE_BOX,
|
|
0,
|
|
Title,
|
|
sizeof(Title),
|
|
NULL
|
|
);
|
|
|
|
if (MessageSize == 0 ) {
|
|
pTitle = SCM_NAMEW;
|
|
}
|
|
else {
|
|
pTitle = Title;
|
|
}
|
|
|
|
if (ScGlobalLastKnownGood & REVERTED_TO_LKG) {
|
|
|
|
MessageSize = FormatMessageW(
|
|
FORMAT_MESSAGE_FROM_HMODULE,
|
|
(LPVOID) NetEventDll,
|
|
EVENT_RUNNING_LASTKNOWNGOOD,
|
|
0,
|
|
Buffer,
|
|
sizeof(Buffer),
|
|
NULL
|
|
);
|
|
|
|
if (MessageSize != 0) {
|
|
|
|
(void) MessageBoxW(
|
|
NULL,
|
|
Buffer,
|
|
pTitle,
|
|
MB_OK | MB_SETFOREGROUND | MB_ICONEXCLAMATION |
|
|
MB_SYSTEMMODAL | MB_SERVICE_NOTIFICATION
|
|
);
|
|
//
|
|
// Now switch back to proper priority
|
|
//
|
|
(void)NtSetInformationProcess(
|
|
NtCurrentProcess(),
|
|
ProcessBasePriority,
|
|
&NewBasePriority,
|
|
sizeof(NewBasePriority));
|
|
|
|
}
|
|
else {
|
|
SC_LOG1(TRACE, "FormatMessage failed %lu\n", GetLastError());
|
|
}
|
|
|
|
}
|
|
|
|
if (StartFailFlag) {
|
|
|
|
MessageSize = FormatMessageW(
|
|
FORMAT_MESSAGE_FROM_HMODULE,
|
|
(LPVOID) NetEventDll,
|
|
EVENT_SERVICE_START_AT_BOOT_FAILED,
|
|
0,
|
|
Buffer,
|
|
sizeof(Buffer),
|
|
NULL
|
|
);
|
|
|
|
if (MessageSize != 0) {
|
|
|
|
(void) MessageBoxW(
|
|
NULL,
|
|
Buffer,
|
|
pTitle,
|
|
MB_OK | MB_SETFOREGROUND | MB_ICONEXCLAMATION |
|
|
MB_SYSTEMMODAL | MB_SERVICE_NOTIFICATION
|
|
);
|
|
//
|
|
// Now switch back to proper priority
|
|
//
|
|
(void) NtSetInformationProcess(
|
|
NtCurrentProcess(),
|
|
ProcessBasePriority,
|
|
&NewBasePriority,
|
|
sizeof(NewBasePriority));
|
|
}
|
|
else {
|
|
SC_LOG1(TRACE, "FormatMessage failed %lu\n", GetLastError());
|
|
}
|
|
}
|
|
|
|
(void) FreeLibrary(NetEventDll);
|
|
|
|
//
|
|
// Now switch to high priority class
|
|
//
|
|
|
|
ExitThread(0);
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScAddFailedDriver(
|
|
LPWSTR Driver
|
|
)
|
|
{
|
|
DWORD StrSize = WCSSIZE(Driver);
|
|
LPFAILED_DRIVER NewEntry;
|
|
LPFAILED_DRIVER Entry;
|
|
|
|
|
|
NewEntry = (PVOID) LocalAlloc(
|
|
LMEM_ZEROINIT,
|
|
(UINT) sizeof(FAILED_DRIVER) + StrSize
|
|
);
|
|
|
|
if (NewEntry == NULL) {
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
//
|
|
// Each string will be separated from the previous one a CR and
|
|
// LF character. We already included one for NULL terminator of each
|
|
// driver so add one more.
|
|
//
|
|
ScTotalSizeFailedDrivers += StrSize + sizeof(WCHAR);
|
|
|
|
wcscpy((LPWSTR) NewEntry->DriverName, Driver);
|
|
|
|
//
|
|
// Insert new entry into ScFailedDrivers global list
|
|
//
|
|
|
|
//
|
|
// Special case empty list
|
|
//
|
|
if (ScFailedDrivers == NULL) {
|
|
ScFailedDrivers = NewEntry;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//
|
|
// Otherwise look for end of the list and insert new entry
|
|
//
|
|
Entry = ScFailedDrivers;
|
|
|
|
while (Entry->Next != NULL) {
|
|
Entry = Entry->Next;
|
|
}
|
|
|
|
Entry->Next = NewEntry;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
VOID
|
|
ScDestroyFailedDriverList(
|
|
VOID
|
|
)
|
|
{
|
|
LPFAILED_DRIVER DeleteEntry;
|
|
|
|
|
|
while (ScFailedDrivers != NULL) {
|
|
DeleteEntry = ScFailedDrivers;
|
|
ScFailedDrivers = ScFailedDrivers->Next;
|
|
(void) LocalFree((HLOCAL) DeleteEntry);
|
|
}
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScMakeFailedDriversOneString(
|
|
LPWSTR *DriverList
|
|
)
|
|
{
|
|
LPFAILED_DRIVER Entry = ScFailedDrivers;
|
|
|
|
|
|
//
|
|
// Allocate space for concatenated string of all the drivers that
|
|
// failed plus the terminator character.
|
|
//
|
|
*DriverList = (PVOID) LocalAlloc(
|
|
LMEM_ZEROINIT,
|
|
(UINT) ScTotalSizeFailedDrivers + sizeof(WCHAR)
|
|
);
|
|
|
|
if (*DriverList == NULL) {
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
while (Entry != NULL) {
|
|
wcscat(*DriverList, L"\r\n");
|
|
wcscat(*DriverList, (LPWSTR) Entry->DriverName);
|
|
Entry = Entry->Next;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScGroupChangeIsSignaled(
|
|
PVOID pContext,
|
|
DWORD dwWaitStatus
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
if (dwWaitStatus == WAIT_OBJECT_0) {
|
|
|
|
SC_LOG1(THREADS,"Group Change is signaled 0x%lx\n",pContext);
|
|
|
|
ScGroupListLock(SC_GET_EXCLUSIVE);
|
|
ScHandleGroupOrderChange();
|
|
ScGroupListLock(SC_RELEASE);
|
|
|
|
ScResetGroupOrderChange((HANDLE)pContext);
|
|
}
|
|
else {
|
|
SC_LOG1(ERROR,"ScGroupChangeIsSignaled received bad WaitStatus %d\n",
|
|
dwWaitStatus);
|
|
}
|
|
return(0);
|
|
}
|
|
|