/*++ 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 // printf #include #include // DbgPrint prototype #include // DataTypes and runtime APIs #include // generated by the MIDL complier #include // needed for winbase.h #include // LocalAlloc #include // RegisterServicesProcess #include // needed by lmalert.h #include // NetAlertRaiseEx definitions #include // ALERT_SC_IsLastKnownGood #ifdef _CAIRO_ #include // HRESULT #include // ScmCallSvcObject #endif #include // Unicode string macros #include // Rpcp... function prototypes #include // SC_LOG #include // SC_INTERNAL_START_EVENT #include #include "scopen.h" // SC_HANDLE_STRUCT needed by scsec.h #include "scsec.h" // Security object functions #include // 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_RPC_PIPE #include // 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); }