/*++ Copyright (c) 1992 Microsoft Corporation Module Name: BOOTCFG.CXX Abstract: Contains functions used for managing the system control sets in the system portion of the registry. ScCheckLastKnownGood ScRevertToLastKnownGood NotifyBootConfigStatus ScGetTopKeys ScGetCtrlSetIds ScDeleteRegTree ScBuildCtrlSetName ScGetCtrlSetHandle ScDeleteTree ScCopyKeyRecursive ScCopyKeyValues ScDeleteRegServiceEntry ScGatherOrphanIds ScDeleteCtrlSetOrphans ScMatchInArray ScStartCtrlSetCleanupThread ScCleanupThread ScRunAcceptBootPgm ScAcceptTheBoot Author: Dan Lafferty (danl) 19-Apr-1992 Environment: User Mode - Win32 Notes: Revision History: 24-Aug-1998 Elliot Shmukler (t-ellios) Most of the LKG related work has now been moved into the Kernel. The tree copies & clone deletions formerly performed by functions in this file has now been replaced by calls to NtInitializeRegistry. 28-Jun-1995 AnirudhS SetupInProgress: This function is now called from more than one place. Cache the return value so we only examine the registry once. 04-Feb-1994 Danl RevertToLastKnownGood: If the boot has been accepted, then we won't allow a revert. 15-Jun-1993 Danl Ignore LastKnownGood adjustments if setup is still running. Use the SystemSetupInProgress value in the registry to determine if is running. 01-Apr-1993 Danl Add ability to take ownership if we cannot open one of the keys due to an access denied error. 08-Feb-1993 Danl Changed the clearing of the LKG_ENV_VAR so that it is done whenever we are booting LKG. Reguardless of whether or not it is the last boot. Prior to this, it was only cleared when a revert occured, and not on the first boot. 04-Feb-1993 Danl Use NtUnloadKey to delete the clone tree. The clone tree is now in a separate hive. So this is allowed. 18-Jan-1993 Danl Make use of the LastKnownGood Environment Variable. Now we do not alter the default control set when we need to revert. We just set the Environment Variable to True, and reboot. Phase2 and ScCheckLastKnownGood do the right thing. 19-Apr-1992 danl Created --*/ // // INCLUDES // #include "precomp.hxx" #include // ultoa #include "scsec.h" // ScAccessValidate() #include "bootcfg.h" // ScRegDeleteTree() #include "scconfig.h" // ScOpenServicesKey() #include // SetupInProgress() #include // REGSTR_VALUE_OOBEINPROGRESS #include // // DEFINES // #define SYSTEM_KEY L"system" #define SELECT_KEY L"select" #define SERVICES_KEY L"System\\CurrentControlSet\\Services" #define ACCEPT_BOOT_KEY L"System\\CurrentControlSet\\Control\\BootVerificationProgram" #define SETUP_PROG_KEY L"Setup" #define CURRENT_VALUE_NAME L"Current" #define DEFAULT_VALUE_NAME L"Default" #define LKG_VALUE_NAME L"LastKnownGood" #define FAILED_VALUE_NAME L"Failed" #define IMAGE_PATH_NAME L"ImagePath" #define SETUP_PROG_VALUE_NAME L"SystemSetupInProgress" #define CTRL_SET_NAME_TEMPLATE L"ControlSet000" #define CTRL_SET_NAME_CHAR_COUNT 13 #define CTRL_SET_NAME_NUM_OFFSET 10 #define CTRL_SET_NAME_BYTES ((CTRL_SET_CHAR_COUNT+1) * sizeof(WCHAR)) #define CLONE_SECURITY_INFORMATION (OWNER_SECURITY_INFORMATION | \ GROUP_SECURITY_INFORMATION | \ DACL_SECURITY_INFORMATION | \ SACL_SECURITY_INFORMATION) // // STANDARD access is obtained for the system and select keys. // We read and write to these keys. // #define SC_STANDARD_KEY_ACCESS KEY_READ | \ READ_CONTROL | \ WRITE_OWNER | \ KEY_WRITE // // CLONE access is obtained for the top level clone key // We must be able to copy and delete clone trees. // #define SC_CLONE_KEY_ACCESS KEY_READ | \ READ_CONTROL | \ WRITE_OWNER | \ DELETE | \ ACCESS_SYSTEM_SECURITY // // CONTROL_SET access is obtained for the top level control sets. // We must be able to copy and delete control sets. // NOTE: SE_SECURITY_PRIVILEGE is required to get ACCESS_SYSTEM_SECURITY. // #define SC_CONTROL_SET_KEY_ACCESS KEY_READ | \ KEY_WRITE | \ DELETE | \ READ_CONTROL | \ WRITE_OWNER | \ ACCESS_SYSTEM_SECURITY // // COPY access is obtained for each subkey in a control set as it is being // copied. // NOTE: SE_SECURITY_PRIVILEGE is required to get ACCESS_SYSTEM_SECURITY. // #define SC_COPY_KEY_ACCESS KEY_READ | \ READ_CONTROL | \ ACCESS_SYSTEM_SECURITY // // DELETE access is obtained for each subkey in a control set that is being // deleted. // #define SC_DELETE_KEY_ACCESS DELETE | \ KEY_READ // // CREATE access is the access used for all keys created by this // process. // #define SC_CREATE_KEY_ACCESS KEY_WRITE | \ WRITE_OWNER | \ WRITE_DAC | \ ACCESS_SYSTEM_SECURITY // // Control Set IDs are stored in an array of DWORDs. The array has the // following offsets for each ID: // #define CURRENT_ID 0 #define DEFAULT_ID 1 #define LKG_ID 2 #define FAILED_ID 3 #define NUM_IDS 4 // // Macros // #define SET_LKG_ENV_VAR(pString) \ { \ UNICODE_STRING Name,Value; \ \ RtlInitUnicodeString(&Name, L"LastKnownGood"); \ RtlInitUnicodeString(&Value,pString); \ \ status = RtlNtStatusToDosError(NtSetSystemEnvironmentValue(&Name,&Value)); \ } // // GLOBALS // // // This flag is set when ScCheckLastKnownGood is called. It is later // checked when either ScRevertToLastKnownGood or NotifyBootConfigStatus // is called. TRUE indicates that we know we are booting LastKnownGood. // DWORD ScGlobalLastKnownGood; BOOL ScGlobalBootAccepted = FALSE; CRITICAL_SECTION ScBootConfigCriticalSection; LPDWORD ScGlobalOrphanIds = NULL; // // LOCAL FUNCTION PROTOTYPES // DWORD ScGetTopKeys( PHKEY SystemKey, PHKEY SelectKey ); DWORD ScGetCtrlSetIds( HKEY SelectKey, LPDWORD IdArray ); BOOL ScBuildCtrlSetName( LPWSTR ControlSetName, DWORD ControlId ); HKEY ScGetCtrlSetHandle( HKEY SystemKey, DWORD ControlId, LPWSTR ControlSetName ); VOID ScDeleteTree( IN HKEY KeyHandle ); VOID ScCopyKeyRecursive( HKEY ParentKey, PHKEY DestKeyPtr, HKEY SourceKey, LPWSTR DestKeyName ); VOID ScCopyKeyValues( HKEY DestKey, HKEY SourceKey, DWORD NumberOfValues, DWORD MaxValueNameLength, DWORD MaxValueDataLength ); VOID ScDeleteRegTree( HKEY ParentKey, HKEY KeyToDelete, LPWSTR NameOfKeyToDelete ); VOID ScGatherOrphanIds( HKEY SystemKey, LPDWORD *OrphanIdPtr, LPDWORD idArray ); BOOL ScMatchInArray( DWORD Value, LPDWORD IdArray ); VOID ScStartCtrlSetCleanupThread(); DWORD ScCleanupThread(); DWORD ScAcceptTheBoot( VOID ); DWORD ScGetNewCtrlSetId( LPDWORD IdArray, LPDWORD NewIdPtr ); BOOL ScCheckLastKnownGood( VOID ) /*++ Routine Description: This function is called early in the service controller initialization. Its purpose is to protect the LastKnownGood control set. If this function finds that the control set that we are booting is the LastKnownGood control set, it will save the clone tree to a new control set and make this LastKnownGood. The clone tree in this case is an unchanged version of LKG. The Current control is not! Current may have been modified by drivers that were started before the service controller was started. Phase 2 of the boot procedure is always responsible for actually doing the revert to LastKnownGood. We determine that we have reverted by noting that Current and LKG will be the same control sets, and Default will be different. If Default is the same (all three control sets are the same), then it is the very first boot, and we don't consider it a failure case. If Phase 2 is causing the boot from LastKnownGood, then we want to set Failed to Default and Current to LKG and Set the LKG environment variable to FALSE. The assumption here is that Phase2 is using LastKnownGood because The Default Control Set was not acceptable. Arguments: TRUE - If all the necessary operations were successful. FALSE - If any of the control set manipulation could not be completed successfully. Return Value: Note: --*/ { DWORD status; BOOL retStat; HKEY systemKey=0; HKEY selectKey=0; HKEY failedKey=0; HKEY newKey=0; DWORD idArray[NUM_IDS]; WCHAR failedKeyName[CTRL_SET_NAME_CHAR_COUNT+1]; DWORD savedLkgId; DWORD newId; ULONG privileges[5]; // // Initialize the Critical section that will synchronize access to // these routines. The service controller could call // ScRevertToLastKnownGood at the same time that someone calls // NotifyBootConfigStatus(). This could cause the control set pointers // to get corrupted. So access to these functions is restricted by // a critical section. It is initialized here because this function // must be called prior to starting any services, or starting the // RPC server. Therefore we can't get asynchronous calls to these // routines at this time. // InitializeCriticalSection(&ScBootConfigCriticalSection); // // This thread gets SE_SECURITY_PRIVILEGE for copying security // descriptors and deleting keys. // privileges[0] = SE_BACKUP_PRIVILEGE; privileges[1] = SE_RESTORE_PRIVILEGE; privileges[2] = SE_SECURITY_PRIVILEGE; privileges[3] = SE_TAKE_OWNERSHIP_PRIVILEGE; privileges[4] = SE_SYSTEM_ENVIRONMENT_PRIVILEGE; status = ScGetPrivilege( 5, privileges); if (status != NO_ERROR) { SC_LOG1(ERROR, "ScCheckLastKnownGood: ScGetPrivilege Failed %d\n", status); return(FALSE); } // // Get the System, Select, and Clone Keys // status = ScGetTopKeys(&systemKey, &selectKey); if (status != NO_ERROR) { SC_LOG0(ERROR,"ScCheckLastKnownGood: ScGetTopKeys failed\n"); retStat = FALSE; goto CleanExit; } // // Get the ControlSetIds stored in the \system\select key. // status = ScGetCtrlSetIds( selectKey, idArray); if (status != NO_ERROR) { ScRegCloseKey(systemKey); ScRegCloseKey(selectKey); SC_LOG0(ERROR,"ScCheckLastKnownGood: ScGetCtrlSetIds Failed\n"); retStat = FALSE; goto CleanExit; } // // Scan for Orphaned Control Sets. // This is required prior to calling ScGetNewCtrlSetId (which // avoids the orphaned numbers). // ScGatherOrphanIds(systemKey,&ScGlobalOrphanIds,idArray); if ((SetupInProgress(systemKey, NULL)) || (idArray[CURRENT_ID] != idArray[LKG_ID])) { // // We are not booting from LastKnownGood, so we don't do // anything except make sure the LKG_FLAG not set. // ScGlobalLastKnownGood = 0; ScRegCloseKey(systemKey); ScRegCloseKey(selectKey); retStat = TRUE; goto CleanExit; } else { // // We Must be booting the LastKnownGood configuration. // Put LkgControlSetId into SavedLkgControlSetId. // SC_LOG0(TRACE,"ScCheckLastKnownGood, We are booting LKG\n"); savedLkgId = idArray[LKG_ID]; // // Set the LKG environment variable to FALSE - so Phase 2 // does not automatically revert again. // SET_LKG_ENV_VAR(L"False"); if (status != NO_ERROR) { SC_LOG1(ERROR,"ScCheckLastKnownGood: Couldn't clear LKG " "environment variable %d\n",status); } // // Copy the Clone tree into a non-volatile node (new ControlSetId). // SC_LOG0(TRACE,"ScCheckLastKnownGood, Copy Clone to new ctrl set\n"); status = ScGetNewCtrlSetId( idArray, &newId); if(status == NO_ERROR) { status = RtlNtStatusToDosError(NtInitializeRegistry(REG_INIT_BOOT_ACCEPTED_BASE + (USHORT)newId)); } if (status != NO_ERROR) { SC_LOG0(ERROR,"ScCheckLastKnownGood: ScGetNewCtrlSetId Failed\n"); SC_LOG0(ERROR,"SERIOUS ERROR - Unable to copy control set that " "is to be saved as LastKnownGood\n"); } else { SC_LOG0(TRACE,"ScCheckLastKnownGood, Copy Clone is complete\n"); // // Set LkgControlSetId to this new ControlSetId. // SC_LOG0(TRACE,"ScCheckLastKnownGood, Set LKG to this new ctrl set\n"); idArray[LKG_ID] = newId; status = ScRegSetValueExW( selectKey, // hKey LKG_VALUE_NAME, // lpValueName 0, // dwValueTitle (OPTIONAL) REG_DWORD, // dwType (LPBYTE)&(idArray[LKG_ID]), // lpData sizeof(DWORD)); // cbData ScRegCloseKey(newKey); if (status != NO_ERROR) { SC_LOG1(ERROR,"ScCheckLastKnownGood: ScRegSetValueEx (lkgValue) " "failed %d\n",status); SC_LOG1(ERROR,"Semi-SERIOUS ERROR - Unable to Set Select Value " "For LastKnownGood.\nThe new ControlSet%d should " "be LKG\n",newId); } else { // // Since we already generated a LKG, we don't want to allow the // user or the boot verfication program to try to go through the // motions of generating it again. So we set the global flag that // indicates that the boot was accepted as LKG. // ScGlobalBootAccepted = TRUE; // // Set Global LKG_FLAG to indicate that we are running LKG, and // whether or not we are here because we reverted. The only // reason we would be here without reverting is because it is the // very first boot. But in the very first boot, FAILED is 0. // ScGlobalLastKnownGood |= RUNNING_LKG; if (idArray[FAILED_ID] != 0) { ScGlobalLastKnownGood |= REVERTED_TO_LKG; } } //endif - Set LKG Id to NetCtrlSet ID; } //endif - MakeNewCtrlSet == TRUE; // // If the DefaultControlSetId is the same as the original // LkgControlSetId, then Phase2 of the boot must have reverted // to Last Known Good. // if (idArray[DEFAULT_ID] != savedLkgId) { // // We are booting LastKnownGood because it was set that way // by Phase2 of the boot. In this case, we want to set the // FailedControlSetId to the DefaultControlSetId. Then we // want to set the DefaultControlSetId to the CurrentControlSetId. // // NOTE: On the very first boot, we don't go through this path // because current=default=lkg. // SC_LOG0(TRACE,"ScCheckLastKnownGood, Phase 2 caused LKG" " so we delete the failed tree and put\n" " Default->Failed\n" " Lkg -> Default\n"); if (idArray[FAILED_ID] != 0) { SC_LOG0(TRACE,"ScCheckLastKnownGood: Deleting Old Failed Tree\n"); failedKey = ScGetCtrlSetHandle( systemKey, idArray[FAILED_ID], failedKeyName); ScDeleteRegTree(systemKey, failedKey, failedKeyName); } // // Put the DefaultId into the Failed value. // idArray[FAILED_ID] = idArray[DEFAULT_ID]; status = ScRegSetValueExW( selectKey, // hKey FAILED_VALUE_NAME, // lpValueName 0, // dwValueTitle (OPTIONAL) REG_DWORD, // dwType (LPBYTE)&(idArray[FAILED_ID]), // lpData sizeof(DWORD)); // cbData if (status != NO_ERROR) { SC_LOG1(ERROR,"ScCheckLastKnownGood: ScRegSetValueEx (failedValue) failed %d\n", status); } // // Put the CurrentId into the Default Value. // idArray[DEFAULT_ID] = idArray[CURRENT_ID]; status = ScRegSetValueExW( selectKey, // hKey DEFAULT_VALUE_NAME, // lpValueName 0, // dwValueTitle (OPTIONAL) REG_DWORD, // dwType (LPBYTE)&(idArray[CURRENT_ID]), // lpData sizeof(DWORD)); // cbData if (status != NO_ERROR) { SC_LOG1(ERROR,"ScCheckLastKnownGood: ScRegSetValueEx (DefaultValue) failed %d\n", status); ScRegCloseKey(selectKey); ScRegCloseKey(systemKey); retStat = FALSE; goto CleanExit; } } ScRegCloseKey(systemKey); ScRegCloseKey(selectKey); } retStat = TRUE; CleanExit: // // If the code above was successful then mark the boot as having been // successful. // if(retStat) { HANDLE bootStatusData; BOOL b = TRUE; status = RtlLockBootStatusData(&bootStatusData); if(NT_SUCCESS(status)) { RtlGetSetBootStatusData(bootStatusData, FALSE, RtlBsdItemBootGood, &b, sizeof(BOOL), NULL); RtlUnlockBootStatusData(bootStatusData); } } // // Restore privileges for the current thread. // (VOID)ScReleasePrivilege(); // // Remove any control sets that need to be deleted (clone or orphans). // This is performed by a seperate thread. // if (ScGlobalOrphanIds != NULL) { ScStartCtrlSetCleanupThread(); } return(retStat); } DWORD ScRevertToLastKnownGood( VOID ) /*++ Routine Description: This function attempts to revert to the last known good control set. It does this in the following manner: If not running LastKnownGood: Set the LKG environment variable so that phase 2 of the boot procedure will cause the revert to happen. Then shutdown the system so it will boot again. Arguments: Return Value: Note: --*/ { DWORD status; NTSTATUS ntStatus; ULONG privileges[6]; // // If we are not currently running LastKnownGood, then set the tree we // are booting from (clone) to failed. Set the Default to point to // LastKnownGood. Then reboot. // if (!(ScGlobalLastKnownGood & RUNNING_LKG)) { EnterCriticalSection(&ScBootConfigCriticalSection); if (ScGlobalBootAccepted) { // // If the boot has already been accepted, then we don't want // to allow a forced revert. // LeaveCriticalSection(&ScBootConfigCriticalSection); return(ERROR_BOOT_ALREADY_ACCEPTED); } SC_LOG0(TRACE,"ScRevertToLastKnownGood: Reverting...\n"); // // This thread gets SE_SECURITY_PRIVILEGE for copying security // descriptors and deleting keys. // privileges[0] = SE_BACKUP_PRIVILEGE; privileges[1] = SE_RESTORE_PRIVILEGE; privileges[2] = SE_SECURITY_PRIVILEGE; privileges[3] = SE_SHUTDOWN_PRIVILEGE; privileges[4] = SE_SYSTEM_ENVIRONMENT_PRIVILEGE; privileges[5] = SE_TAKE_OWNERSHIP_PRIVILEGE; status = ScGetPrivilege( 6, privileges); if (status != NO_ERROR) { SC_LOG1(ERROR, "ScRevertToLastKnownGood: ScGetPrivilege Failed %d\n", status); LeaveCriticalSection(&ScBootConfigCriticalSection); return(status); } // // Set the LKG environment variable to True - so Phase 2 // will automatically revert, or put up the screen asking if the // user wants to revert. // SET_LKG_ENV_VAR(L"True"); if (status != NO_ERROR) { // // If we could not set the environment variable that causes // the revert, there is no reason to reboot. Otherwise, we // we would reboot continuously. // // WE SHOULD LOG AN EVENT HERE - that says that we should // reboot, but we didn't. // SC_LOG1(ERROR,"RevertToLastKnownGood: Couldn't set LKG " "environment variable %d\n",status); (VOID)ScReleasePrivilege(); LeaveCriticalSection(&ScBootConfigCriticalSection); return(NO_ERROR); } // // Re-boot. // SC_LOG0(ERROR,"Reverted To LastKnownGood. Now Rebooting...\n"); ScLogEvent(NEVENT_REVERTED_TO_LASTKNOWNGOOD); // // Just prior to shutting down, sleep for 5 seconds so that the // system has time to flush the events to disk. // Sleep(5000); LeaveCriticalSection(&ScBootConfigCriticalSection); ntStatus = NtShutdownSystem(ShutdownReboot); if (!NT_SUCCESS(ntStatus)) { SC_LOG1(ERROR,"NtShutdownSystem Failed 0x%lx\n",ntStatus); } // // Restore privileges for the current thread. // (VOID)ScReleasePrivilege(); return(RtlNtStatusToDosError(ntStatus)); } // // Otherwise... just return back to the caller. // return(ERROR_ALREADY_RUNNING_LKG); } DWORD RNotifyBootConfigStatus( IN LPWSTR lpMachineName, IN DWORD BootAcceptable ) /*++ Routine Description: If we are not currently booted with Last Known Good, this function will revert to Last Known Good if the boot is not acceptable. Or it will save the boot configuration that we last booted from as the Last Known Good. This is the configuration that we will fall back to if a future boot fails. Arguments: BootAcceptable - This indicates whether or not the boot was acceptable. Return Value: TRUE - This is only returned if the boot is acceptable, and we successfully replaced Last Known Good with the current boot configuration. FALSE - This is returned if an error occured when attempting to replace Last Known Good or if the system is currently booted from Last Known Good. Note: --*/ { DWORD status=NO_ERROR; SC_HANDLE_STRUCT scManagerHandle; UNREFERENCED_PARAMETER(lpMachineName); // This should always be null. // // Perform a security check to see if the caller has // SC_MANAGER_MODIFY_BOOT_CONFIG access. // scManagerHandle.Signature = SC_SIGNATURE; scManagerHandle.Type.ScManagerObject.DatabaseName = NULL; status = ScAccessValidate(&scManagerHandle,SC_MANAGER_MODIFY_BOOT_CONFIG); if (status != NO_ERROR) { return(status); } if (ScGlobalLastKnownGood & RUNNING_LKG) { // // If we are already booting LastKnownGood, then return false. // return(ERROR_ALREADY_RUNNING_LKG); } if (BootAcceptable) { SC_LOG0(TRACE,"NotifyBootConfigStatus: Boot is Acceptable\n"); // // Must enter critical section before progressing. // EnterCriticalSection(&ScBootConfigCriticalSection); if (ScGlobalBootAccepted) { LeaveCriticalSection(&ScBootConfigCriticalSection); return(ERROR_BOOT_ALREADY_ACCEPTED); } // // If Auto-Start is not complete yet, then we just want to mark // to boot as accepted and operate on it after auto-start completion. // We also want to set the ScGlobalBootAccepted flag so that // further requests to accept the boot will be ignored. // if (!(ScGlobalLastKnownGood & AUTO_START_DONE)) { SC_LOG0(BOOT,"RNotifyBootConfigStatus: Boot Accepted, but Auto-start " "is not complete. Defer acceptance\n"); ScGlobalLastKnownGood |= ACCEPT_DEFERRED; ScGlobalBootAccepted = TRUE; } else { SC_LOG0(BOOT,"RNotifyBootConfigStatus: Boot Accepted and Auto-start " "is complete\n"); status = ScAcceptTheBoot(); } LeaveCriticalSection(&ScBootConfigCriticalSection); return(status); } else { // // The Boot was not acceptable. // // NOTE: We should never return from the call to // ScRevertToLastKnownGood. // // SC_LOG0(TRACE,"NotifyBootConfigStatus: Boot is Not Acceptable. Revert!\n"); return(ScRevertToLastKnownGood()); } } DWORD ScGetTopKeys( PHKEY SystemKey, PHKEY SelectKey ) /*++ Routine Description: This function opens handles to the SystemKey, and the SelectKey. Arguments: Return Value: Note: --*/ { DWORD status; // // Get the System Key // status = ScRegOpenKeyExW( HKEY_LOCAL_MACHINE, // hKey SYSTEM_KEY, // lpSubKey 0L, // ulOptions (reserved) SC_STANDARD_KEY_ACCESS, // desired access SystemKey); // Newly Opened Key Handle if (status != NO_ERROR) { SC_LOG1(ERROR,"ScGetTopKeys: ScRegOpenKeyEx (system key) failed %d\n",status); return (status); } // // Get the Select Key // status = ScRegOpenKeyExW( *SystemKey, // hKey SELECT_KEY, // lpSubKey 0L, // ulOptions (reserved) SC_STANDARD_KEY_ACCESS, // desired access SelectKey); // Newly Opened Key Handle if (status != NO_ERROR) { SC_LOG1(ERROR,"ScGetTopKeys: ScRegOpenKeyEx (select key) failed %d\n",status); ScRegCloseKey(*SystemKey); return (status); } return(NO_ERROR); } DWORD ScGetCtrlSetIds( HKEY SelectKey, LPDWORD IdArray ) /*++ Routine Description: This function obtains all the important Control Set IDs from the \system\select portion of the registry. These IDs are in the form of a DWORD that is used to build the Key name for that control set. For instance the DWORD=003 is used to build the string "control_set_003". If a control set for one of these is not present, a 0 is returned for that ID. Arguments: SelectKey - This is the Key Handle for the \system\select portion of the registry. IdArray - This is an array of DWORDs where each element is an ID. This array contains elements for Current, Default, LKG, and Failed. Return Value: NO_ERROR - If the operation was successful. OTHER - Any error that can be returned from RegQueryValueEx could be returned here if we fail to get an ID for Current, Default, or LKG. We expect Failed To be empty to start with. Note: --*/ { DWORD status; DWORD bufferSize; // // Get the Current Id // bufferSize = sizeof(DWORD); status = ScRegQueryValueExW ( SelectKey, // hKey CURRENT_VALUE_NAME, // lpValueName NULL, // lpTitleIndex NULL, // lpType (LPBYTE)&IdArray[CURRENT_ID], // lpData &bufferSize); // lpcbData if (status != NO_ERROR) { SC_LOG1(ERROR,"ScGetCtrlSetIds,ScRegQueryValueEx(current) failed %d\n",status); IdArray[CURRENT_ID] = 0; return(status); } // // Get the DefaultID // bufferSize = sizeof(DWORD); status = ScRegQueryValueExW ( SelectKey, // hKey DEFAULT_VALUE_NAME, // lpValueName NULL, // lpTitleIndex NULL, // lpType (LPBYTE)&IdArray[DEFAULT_ID], // lpData &bufferSize); // lpcbData if (status != NO_ERROR) { SC_LOG1(ERROR,"ScGetCtrlSetIds,ScRegQueryValueEx(default) failed %d\n",status); IdArray[DEFAULT_ID] = 0; return(status); } // // Get the LKG Id // bufferSize = sizeof(DWORD); status = ScRegQueryValueExW ( SelectKey, // hKey LKG_VALUE_NAME, // lpValueName NULL, // lpTitleIndex NULL, // lpType (LPBYTE)&IdArray[LKG_ID], // lpData &bufferSize); // lpcbData if (status != NO_ERROR) { SC_LOG1(ERROR,"ScGetCtrlSetIds,ScRegQueryValueEx(LKG) failed %d\n",status); IdArray[LKG_ID] = 0; return(status); } // // Get the Failed Id // bufferSize = sizeof(DWORD); status = ScRegQueryValueExW ( SelectKey, // hKey FAILED_VALUE_NAME, // lpValueName NULL, // lpTitleIndex NULL, // lpType (LPBYTE)&IdArray[FAILED_ID], // lpData &bufferSize); // lpcbData if (status != NO_ERROR) { SC_LOG1(ERROR,"ScGetCtrlSetIds,ScRegQueryValueEx(Failed) failed %d\n",status); IdArray[FAILED_ID] = 0; } return(NO_ERROR); } VOID ScDeleteRegTree( HKEY ParentKey, HKEY KeyToDelete, LPWSTR NameOfKeyToDelete ) /*++ Routine Description: This function walks through a Key Tree and deletes all the sub-keys contained within. It then closes the top level Key Handle, and deletes that key (which is a subkey of the parent). This function also closes the handle for the key being deleted. Arguments: ParentKey - This is the handle to the parent key whose sub-key is being deleted. KeyToDelete - A handle to the key that is to be deleted. NameOfKeyToDelete - This is a pointer to a string that Identifies the name of the key that is to be deleted. Return Value: none. Note: --*/ { DWORD status; if (KeyToDelete == NULL) { return; } // // Delete the tree. // ScDeleteTree(KeyToDelete); ScRegCloseKey(KeyToDelete); status = ScRegDeleteKeyW(ParentKey, NameOfKeyToDelete); if (status != NO_ERROR) { SC_LOG1(ERROR,"ScDeleteRegTree, ScRegDeleteKey failed %d\n",status); } return; } BOOL ScBuildCtrlSetName( LPWSTR ControlSetName, DWORD ControlId ) /*++ Routine Description: Arguments: Return Value: Note: --*/ { DWORD NumOffset = CTRL_SET_NAME_NUM_OFFSET; // // Build the name. NumOffset is the array offset of where the // number portion of the name is to be stored. The number initially // contains 000. And the offset points to the first zero. If only // two digits are to be stored, the offset is first incremented to // point to where the last two digits go. // if (ControlId > 999) { SC_LOG1(ERROR, "ScBuildCtrlSetName,ControlId Too Large -- %d\n",ControlId); return(FALSE); } if (ControlId < 100) { NumOffset++; } if (ControlId < 10) { NumOffset++; } wcscpy(ControlSetName, CTRL_SET_NAME_TEMPLATE); // // The above checks should assure that the _ultow call will not // overflow the buffer. // _ultow(ControlId, &(ControlSetName[NumOffset]), 10); return(TRUE); } HKEY ScGetCtrlSetHandle( HKEY SystemKey, DWORD ControlId, LPWSTR ControlSetName ) /*++ Routine Description: This function uses the ControlId to create the name of the control set to open. Then it opens a Key (handle) to this control set. Then name was well as the key handle are returned. Arguments: SystemKey - This is the handle for the System Key. The Control Sets are sub-keys for this key. ControlId - This is the ID for the Control Set for which we are desiring a handle (key). KeyName - This is a pointer to a location where the name of the key is to be placed. Return Value: HKEY - This is the Key handle for the control set in question. If the control set does not exist, a NULL is returned. Note: --*/ { DWORD status; HKEY ctrlSetKey; // // Build the Control Set Name // if (!ScBuildCtrlSetName(ControlSetName, ControlId)) { return(NULL); } // // Open the Key for this name. // SC_LOG1(TRACE,"ScGetCtrlSetHandle: ControlSetName = "FORMAT_LPWSTR"\n", ControlSetName); // // Get the ControlSetName // status = ScRegOpenKeyExW( SystemKey, // hKey ControlSetName, // lpSubKey 0L, // ulOptions (reserved) SC_CONTROL_SET_KEY_ACCESS, // desired access &ctrlSetKey); // Newly Opened Key Handle if (status != NO_ERROR) { SC_LOG2(ERROR,"ScGetCtrlSetHandle: ScRegOpenKeyEx (%ws) failed %d\n", ControlSetName, status); return (NULL); } return(ctrlSetKey); } DWORD ScGetNewCtrlSetId( LPDWORD IdArray, LPDWORD NewIdPtr ) /*++ Routine Description: This routine computes the new control set ID to be used for the LKG control set Arguments: IdArray - Supplies the ID array filled in by ScGetCtrlSetIds NewIdPtr - Returns a free ID to be used for the LKG control set Return Value: Either NO_ERROR if successful or ERROR_NO_MORE_ITEMS if there are no more free IDs (should never happen) --*/ { DWORD newId, i; BOOL inArray; for(newId = 1; newId < 1000; newId++) { inArray = FALSE; for(i = 0; i < NUM_IDS; i++) { if(IdArray[i] == newId) { inArray = TRUE; break; } } if (!inArray && !ScMatchInArray(newId, ScGlobalOrphanIds)) { *NewIdPtr = newId; return NO_ERROR; } } return ERROR_NO_MORE_ITEMS; } VOID ScDeleteTree( IN HKEY KeyHandle ) /*++ Routine Description: This function recursively deletes all keys under the key handle that is passed in. Arguments: KeyHandle - This is the handle for the Key Tree that is being deleted. Return Value: none. Note: This was cut & pasted from ..\..\winreg\tools\crdel\crdel.c The only modifications were changing TSTR to WSTR and calling the UNICODE version of the functions. --*/ { DWORD status; DWORD Index; HKEY ChildHandle; DWORD bytesReturned; BYTE buffer[ sizeof( KEY_FULL_INFORMATION) + sizeof( WCHAR) * MAX_PATH]; DWORD NumberOfSubKeys; PWCHAR KeyName; status = NtQueryKey( (HANDLE)KeyHandle, KeyFullInformation, (PVOID)buffer, sizeof( buffer), &bytesReturned ); if ( status != STATUS_SUCCESS) { SC_LOG1(ERROR, "ScDeleteTree: NtQueryKey Failed 0x%x\n",status); return; } NumberOfSubKeys = ((PKEY_FULL_INFORMATION)buffer)->SubKeys; KeyName = (PWCHAR)buffer; for( Index = 0; Index < NumberOfSubKeys; Index++ ) { status = ScRegEnumKeyW( KeyHandle, 0, KeyName, sizeof( buffer) ); if (status != NO_ERROR) { SC_LOG1(ERROR, "ScDeleteTree: ScRegEnumKeyW Failed %d\n",status); return; } status = ScRegOpenKeyExW( KeyHandle, KeyName, REG_OPTION_RESERVED, SC_DELETE_KEY_ACCESS, &ChildHandle ); if (status != NO_ERROR) { SC_LOG2(ERROR, "ScDeleteTree: ScRegOpenKeyExW (%ws) Failed %d\n", KeyName, status); return; } ScDeleteTree( ChildHandle ); status = ScRegDeleteKeyW( KeyHandle, KeyName); NtClose( (HANDLE)ChildHandle); if ( status != NO_ERROR) { SC_LOG1(ERROR, "ScDeleteTree: ScRegDeleteKeyW Failed 0x%x\n", status); return; } } } #if 0 VOID ScCopyKeyRecursive( HKEY ParentKey, PHKEY DestKeyPtr, HKEY SourceKey, LPWSTR DestKeyName ) /*++ Routine Description: This function copies the values from the source key to the destination key. Then it goes through each subkey of the source key and creates subkeys for the dest key. This function is then called to copy info for those subkeys. Arguments: ParentKey - This is the Key Handle for the parent key of the destination key. DestKeyPtr - This is the Key Handle for the destination key. SourceKey - This is the key handle for the source key. DestKeyName - This is the name that the new dest should have. Return Value: none - If this operation fails anywhere along the tree, it will simply stop. The tree will be truncated at that point. --*/ { DWORD status; DWORD i; HKEY SourceChildKey; HKEY DestChildKey; WCHAR KeyName[ MAX_PATH ]; DWORD KeyNameLength; WCHAR ClassName[ MAX_PATH ]; DWORD ClassNameLength; DWORD NumberOfSubKeys; DWORD MaxSubKeyLength; DWORD MaxClassLength; DWORD NumberOfValues; DWORD MaxValueNameLength; DWORD MaxValueDataLength; DWORD SecurityDescriptorLength; LPBYTE SecurityDescriptor = NULL; FILETIME LastWriteTime; DWORD disposition; SECURITY_ATTRIBUTES securityAttributes; ClassNameLength = MAX_PATH; // // Find out how many subKeys and values there are in the source key. // status = ScRegQueryInfoKeyW( SourceKey, ClassName, &ClassNameLength, NULL, &NumberOfSubKeys, &MaxSubKeyLength, &MaxClassLength, &NumberOfValues, &MaxValueNameLength, &MaxValueDataLength, &SecurityDescriptorLength, &LastWriteTime ); if (status != NO_ERROR) { SC_LOG1(ERROR, "ScCopyKeyRecursive: ScRegQueryInfoKey Failed %d\n",status); return; } // // If there is a security descriptor, attempt to copy it. // if (SecurityDescriptorLength != 0) { SC_LOG2(BOOT,"ScCopyKeyRecursive: %ws Key Has Security Desc %d bytes\n", DestKeyName, SecurityDescriptorLength); SecurityDescriptor = (LPBYTE)LocalAlloc( LMEM_ZEROINIT, SecurityDescriptorLength); if (SecurityDescriptor == NULL) { SC_LOG0(ERROR, "ScCopyKeyRecursive: Couldn't alloc memory for " "Security Descriptor.\n"); } else { status = ScRegGetKeySecurity( SourceKey, CLONE_SECURITY_INFORMATION, (PSECURITY_DESCRIPTOR)SecurityDescriptor, &SecurityDescriptorLength); if (status != NO_ERROR) { SC_LOG1(ERROR, "ScCopyKeyRecursive: ScRegGetKeySecurity failed %d\n", status); LocalFree(SecurityDescriptor); SecurityDescriptor = NULL; } else { if (!IsValidSecurityDescriptor(SecurityDescriptor)) { SC_LOG1(ERROR,"SecurityDescriptor for %ws is invalid\n", DestKeyName); LocalFree(SecurityDescriptor); SecurityDescriptor = NULL; } } } } securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); securityAttributes.lpSecurityDescriptor = SecurityDescriptor; securityAttributes.bInheritHandle = FALSE; // // Create the Destination Key. // status = ScRegCreateKeyExW( ParentKey, // hKey DestKeyName, // lpSubKey 0L, // dwTitleIndex ClassName, // lpClass 0, // ulOptions SC_CREATE_KEY_ACCESS, // desired access &securityAttributes, // lpSecurityAttributes (Secur Desc) DestKeyPtr, // phkResult &disposition); // lpulDisposition LocalFree(SecurityDescriptor); SecurityDescriptor = NULL; if (status != NO_ERROR) { SC_LOG1(ERROR,"ScCopyKeyRecursive, ScRegCreateKeyEx failed %d\n",status); return; } // // If there are values in the key and we have // enough information to copy them, then do so // if (NumberOfValues > 0) { // // Copy Values to Dest Key // ScCopyKeyValues( *DestKeyPtr, SourceKey, NumberOfValues, MaxValueNameLength, MaxValueDataLength); } // // For each child key, create a new key in the destination tree. // Then call ScCopyKeyRecursive (this routine) again with the two // key handles. // for (i = 0; i < NumberOfSubKeys; i++) { KeyNameLength = MAX_PATH; status = ScRegEnumKeyW( SourceKey, i, KeyName, KeyNameLength); if (status != NO_ERROR) { SC_LOG1(ERROR,"ScCopyKeyRecursive, ScRegEnumKey failed %d\n",status); return; } status = ScRegOpenKeyExW( SourceKey, KeyName, REG_OPTION_RESERVED, SC_COPY_KEY_ACCESS, &SourceChildKey); if (status != NO_ERROR) { SC_LOG2(ERROR,"ScCopyKeyRecursive, ScRegOpenKeyEx (%ws) failed %d\n", KeyName, status); return; } ScCopyKeyRecursive(*DestKeyPtr, &DestChildKey, SourceChildKey, KeyName); ScRegCloseKey(DestChildKey); ScRegCloseKey(SourceChildKey); } // end-for return; } VOID ScCopyKeyValues( HKEY DestKey, HKEY SourceKey, DWORD NumberOfValues, DWORD MaxValueNameLength, DWORD MaxValueDataLength ) /*++ Routine Description: This function copies all the values stored in the source key to the destination key. Arguments: DestKey - This is the key handle for the destination key. SourceKey - This is the key handle for the source key. NumberOfValues - This is the number of values stored in the source key. MaxValueNameLength - This is the number of bytes of the largest name for a data value. MaxValueDataLength - This is the number of bytes in the largest data value. Return Value: none - If it fails, the rest of the values will not get stored for this key. Note: --*/ { DWORD status; DWORD i; LPBYTE DataPtr = NULL; LPWSTR ValueName = NULL; DWORD ValueNameLength; DWORD Type; DWORD DataLength; // // Add extra onto the lengths because these lengths don't allow // for the NULL terminator. // MaxValueNameLength += sizeof(TCHAR); MaxValueDataLength += sizeof(TCHAR); DataPtr = (LPBYTE)LocalAlloc(LMEM_FIXED, MaxValueDataLength); if (DataPtr == NULL) { SC_LOG1(ERROR,"ScCopyKeyValues, LocalAlloc Failed %d\n",GetLastError()); return; } ValueName = (LPWSTR)LocalAlloc(LMEM_FIXED, MaxValueNameLength); if (ValueName == NULL) { SC_LOG1(ERROR,"ScCopyKeyValues, LocalAlloc Failed %d\n",GetLastError()); LocalFree(DataPtr); return; } for (i=0; i 0) { *OrphanIdPtr = tempIdArray; } else { *OrphanIdPtr = NULL; LocalFree(tempIdArray); } return; } VOID ScDeleteCtrlSetOrphans( VOID ) /*++ Routine Description: This function deletes orphaned control sets if any exist. The control set numbers for these orphaned sets are pointed to by a global memory pointer. If this pointer is non-null, then there are control sets to delete. After deletion, the memory pointed to by this pointer is freed. NOTE: The necessary privileges are expected to be held prior to calling this function. Arguments: none Return Value: none --*/ { DWORD status; DWORD i; HKEY systemKey; HKEY keyToDelete; LPWSTR SystemKeyPath = SYSTEM_KEY; WCHAR nameOfKeyToDelete[CTRL_SET_NAME_CHAR_COUNT+1]; if (ScGlobalOrphanIds != NULL) { // // Open the SYSTEM key in the registry. // status = ScRegOpenKeyExW( HKEY_LOCAL_MACHINE, // hKey SystemKeyPath, // lpSubKey 0L, // ulOptions (reserved) SC_DELETE_KEY_ACCESS, // desired access &systemKey); // Newly Opened Key Handle if (status != NO_ERROR) { SC_LOG2(ERROR,"ScDeleteCtrlSetOrphans: " "ScRegOpenKeyEx (%ws) failed %d\n",SystemKeyPath, status); return; } for (i=0; ScGlobalOrphanIds[i]!=0; i++) { // // Use the ID number to get the name and key handle for the // KeyToDelete. // keyToDelete = ScGetCtrlSetHandle( systemKey, ScGlobalOrphanIds[i], nameOfKeyToDelete); // // Delete the entire tree. Then go onto the next ID. // SC_LOG1(TRACE, "ScDeleteCtrlSetOrphans, Delete orphan control set %d\n", ScGlobalOrphanIds[i]); ScDeleteRegTree(systemKey, keyToDelete, nameOfKeyToDelete); SC_LOG0(TRACE,"ScDeleteCtrlSetOrphans, Finished Deleting orphan control set\n"); } // // Free memory for IDs, and set the global pointer to NULL. // LocalFree(ScGlobalOrphanIds); ScGlobalOrphanIds = NULL; } return; } BOOL ScMatchInArray( DWORD Value, LPDWORD Array ) /*++ Routine Description: This function scans through a null terminated array of DWORDs looking for a match with the DWORD value that is passed in. Arguments: Value - The DWORD value that we are looking for. Array - The pointer to the Array of DWORDs that we are scanning through. Return Value: TRUE - If a the Value is found in the Array. FALSE - If it is not found. --*/ { DWORD i; if (Array != NULL) { for(i=0; Array[i] != 0; i++) { if (Value == Array[i]) { return(TRUE); } } } return(FALSE); } VOID ScStartCtrlSetCleanupThread( ) /*++ Routine Description: This function starts a thread that will delete delete any orphaned control sets. Arguments: NONE. Return Value: none --*/ { DWORD status; HANDLE threadHandle; DWORD threadId; threadHandle = CreateThread ( NULL, // Thread Attributes. 0L, // Stack Size (LPTHREAD_START_ROUTINE)ScCleanupThread,// lpStartAddress (LPVOID)0L, // lpParameter 0L, // Creation Flags &threadId); // lpThreadId if (threadHandle == (HANDLE) NULL) { SC_LOG1(ERROR,"ScStartCtrlSetCleanupThread:CreateThread failed %d\n", GetLastError()); // // If we couldn't create the thread for some reason, then just // go ahead and to the cleanup with this thread. This may make // booting the system slow, but it's the best I can do. // status = ScCleanupThread(); } else { CloseHandle(threadHandle); } } DWORD ScCleanupThread( ) /*++ Routine Description: This functions looks through the system key to see if there are any orphan control sets to delete. If found, the orphans are deleted. Orphaned control sets are control sets that exist in the system key, but are not referenced in the \system\select key. NOTE: This function should only be called when no other threads are creating control sets. Otherwise, this function may see a new control set that is not yet in the select key, and attempt to delete it. Arguments: NONE. Return Value: none. --*/ { DWORD status; HKEY systemKey=0; HKEY selectKey=0; DWORD idArray[NUM_IDS]; ULONG privileges[4]; // // This thread gets SE_SECURITY_PRIVILEGE for copying security // descriptors and deleting keys. // privileges[0] = SE_BACKUP_PRIVILEGE; privileges[1] = SE_RESTORE_PRIVILEGE; privileges[2] = SE_SECURITY_PRIVILEGE; privileges[3] = SE_TAKE_OWNERSHIP_PRIVILEGE; status = ScGetPrivilege( 4, privileges); if (status != NO_ERROR) { SC_LOG1(ERROR, "ScCheckLastKnownGood: ScGetPrivilege Failed %d\n", status); return(FALSE); } EnterCriticalSection(&ScBootConfigCriticalSection); // // Get the System, Select, and Clone Keys // status = ScGetTopKeys(&systemKey, &selectKey); if (status != NO_ERROR) { SC_LOG0(ERROR,"ScCleanupThread: ScGetTopKeys failed\n"); LeaveCriticalSection(&ScBootConfigCriticalSection); goto CleanExit; } // // Get the ControlSetIds stored in the \system\select key. // status = ScGetCtrlSetIds( selectKey, idArray); if (status != NO_ERROR) { SC_LOG0(ERROR,"ScCleanupThread: ScGetCtrlSetIds Failed\n"); LeaveCriticalSection(&ScBootConfigCriticalSection); goto CleanExit; } // // Scan for Orphaned Control Sets. // ScGatherOrphanIds(systemKey,&ScGlobalOrphanIds,idArray); LeaveCriticalSection(&ScBootConfigCriticalSection); if (ScGlobalOrphanIds != NULL) { ScDeleteCtrlSetOrphans(); } CleanExit: if (systemKey != 0) { ScRegCloseKey(systemKey); } if (selectKey != 0) { ScRegCloseKey(selectKey); } (VOID)ScReleasePrivilege(); return(0); } VOID ScRunAcceptBootPgm( VOID ) /*++ Routine Description: This function is called after the Service Controller has finished auto-starting all the auto-start services. If the boot has already been accepted (for instance, WinLogon already called NotifyBootConfigStatus()), then at this point we can accept the boot. If the boot has not yet been accepted, this function looks in the ACCEPT_BOOT_KEY portion of the registry to see if there is a value containing the image path of the boot verify program to execute. The program can have any name or path. If it is in the registry, this function will run it. This function is called when the service controller thinks that the boot has completed successfully. It is up to the exec'd program to decide if this is true or not, and take appropriate action if necessary. The default boot verify program will simply accept the boot as is. Arguments: none Return Value: none --*/ { DWORD status; LPWSTR AcceptBootKeyPath = ACCEPT_BOOT_KEY; HKEY AcceptBootKey; DWORD ValueType; LPWSTR pTempImagePath; LPWSTR pImagePath; PROCESS_INFORMATION processInfo; STARTUPINFOW StartupInfo; DWORD bufferSize; DWORD charCount; // // Check to see if the boot has already been accepted. // EnterCriticalSection(&ScBootConfigCriticalSection); ScGlobalLastKnownGood |= AUTO_START_DONE; if (ScGlobalLastKnownGood & ACCEPT_DEFERRED) { SC_LOG0(BOOT,"ScRunAcceptBootPgm: Boot Acceptance was deferred. Accept " "it now\n"); ScAcceptTheBoot(); LeaveCriticalSection(&ScBootConfigCriticalSection); return; } LeaveCriticalSection(&ScBootConfigCriticalSection); // // Open the \CurrentControlSet\Control\AcceptBootPgm Key // // // Get the System Key // status = ScRegOpenKeyExW( HKEY_LOCAL_MACHINE, // hKey AcceptBootKeyPath, // lpSubKey 0L, // ulOptions (reserved) KEY_READ, // desired access &AcceptBootKey); // Newly Opened Key Handle if (status != NO_ERROR) { SC_LOG2(TRACE,"ScRunAcceptBootPgm: ScRegOpenKeyEx (%ws) failed %d\n", AcceptBootKeyPath, status); return; } // // If the ImagePath value is there, then run the specified // program. // bufferSize = MAX_PATH * sizeof(WCHAR); pTempImagePath = (LPWSTR)LocalAlloc(LMEM_FIXED, bufferSize*2); if (pTempImagePath == NULL) { SC_LOG0(TRACE,"ScRunAcceptBootPgm,LocalAlloc failed \n"); return; } pImagePath = pTempImagePath + MAX_PATH; status = ScRegQueryValueExW ( AcceptBootKey, // hKey IMAGE_PATH_NAME, // lpValueName NULL, // lpTitleIndex &ValueType, // lpType (LPBYTE)pTempImagePath, // lpData &bufferSize); // lpcbData if (status != NO_ERROR) { SC_LOG1(TRACE,"ScRunAcceptBootPgm,ScRegQueryValueEx failed %d\n",status); ScRegCloseKey(AcceptBootKey); LocalFree(pTempImagePath); return; } SC_LOG1(TRACE,"ScRunAcceptBootPgm:Executing the %ws program\n",pTempImagePath); if ((ValueType == REG_SZ) || (ValueType == REG_EXPAND_SZ)) { if (ValueType == REG_EXPAND_SZ) { charCount = ExpandEnvironmentStringsW ( pTempImagePath, pImagePath, MAX_PATH); if (charCount > MAX_PATH) { SC_LOG0(ERROR,"ScRunAcceptBootPgm: ImagePath is too big\n"); LocalFree(pTempImagePath); return; } } else { pImagePath = pTempImagePath; } // // Exec the program. // StartupInfo.cb = sizeof(STARTUPINFOW); // size StartupInfo.lpReserved = NULL; // lpReserved StartupInfo.lpDesktop = NULL; // DeskTop StartupInfo.lpTitle = NULL; // Title StartupInfo.dwX = 0; // X (position) StartupInfo.dwY = 0; // Y (position) StartupInfo.dwXSize = 0; // XSize (dimension) StartupInfo.dwYSize = 0; // YSize (dimension) StartupInfo.dwXCountChars = 0; // XCountChars StartupInfo.dwYCountChars = 0; // YCountChars StartupInfo.dwFillAttribute = 0; // FillAttributes StartupInfo.dwFlags = STARTF_FORCEOFFFEEDBACK; // Flags - should be STARTF_TASKNOTCLOSABLE StartupInfo.wShowWindow = SW_HIDE; // ShowWindow StartupInfo.cbReserved2 = 0L; // cbReserved StartupInfo.lpReserved2 = NULL; // lpReserved if (!CreateProcessW ( pImagePath, // Fully qualified image name L"", // Command Line NULL, // Process Attributes NULL, // Thread Attributes FALSE, // Inherit Handles DETACHED_PROCESS, // Creation Flags NULL, // Pointer to Environment block NULL, // Pointer to Current Directory &StartupInfo, // Startup Info &processInfo)) // ProcessInformation { status = GetLastError(); SC_LOG1(ERROR, "ScRunAcceptBootPgm: CreateProcess failed " FORMAT_DWORD "\n", status); } } LocalFree(pTempImagePath); ScRegCloseKey(AcceptBootKey); return; } DWORD ScAcceptTheBoot( VOID ) /*++ Routine Description: This function does the actual work of accepting the current boot as the LKG configuration. NOTE: Before the function is called, the ScBootConfigCriticalSection is expected to be entered. Arguments: Return Value: --*/ { DWORD status; HKEY systemKey=0; HKEY selectKey=0; DWORD idArray[NUM_IDS]; DWORD newId; ULONG privileges[4]; // // This thread gets SE_SECURITY_PRIVILEGE for copying security // descriptors and deleting keys. // privileges[0] = SE_BACKUP_PRIVILEGE; privileges[1] = SE_RESTORE_PRIVILEGE; privileges[2] = SE_SECURITY_PRIVILEGE; privileges[3] = SE_TAKE_OWNERSHIP_PRIVILEGE; status = ScGetPrivilege( 4, privileges); if (status != NO_ERROR) { SC_LOG1(ERROR, "RNotifyBootConfigStatus: ScGetPrivilege Failed %d\n", status); return(status); } // // Get the System, Select, and Clone Keys // status = ScGetTopKeys(&systemKey, &selectKey); if (status != NO_ERROR) { SC_LOG0(ERROR,"ScAcceptTheBoot: ScGetTopKeys failed\n"); SetLastError(status); // // Restore privileges for the current thread. // (VOID)ScReleasePrivilege(); return(status); } // // Get the ControlSetIds stored in the \system\select key. // status = ScGetCtrlSetIds( selectKey, idArray); if (status != NO_ERROR) { SC_LOG0(ERROR,"ScAcceptTheBoot: ScGetCtrlSetIds Failed\n"); goto CleanExit; } // // Don't commit the LKG profile if this is safe mode, unless we actually // booted into the LKG profile. // if (g_SafeBootEnabled) { if (idArray[LKG_ID] != idArray[CURRENT_ID]) { ScGlobalBootAccepted = TRUE; status = NO_ERROR; SC_LOG0(TRACE,"ScAcceptTheBoot: Safe mode boot, not committing LKG\n"); goto CleanExit; } } // // Scan for Orphaned Control Sets. // This is required prior to calling ScMakeNewCtrlSet (which // avoids the orphaned numbers). // ScGatherOrphanIds(systemKey,&ScGlobalOrphanIds,idArray); // // Delete the LastKnownGood ControlSet if there are no other // references to that control set. // SC_LOG0(TRACE,"ScAcceptTheBoot: Delete LKG ControlSet if no ref\n"); if ( (idArray[LKG_ID] != idArray[FAILED_ID]) && (idArray[LKG_ID] != idArray[DEFAULT_ID]) && (idArray[LKG_ID] != idArray[CURRENT_ID])) { newId = idArray[LKG_ID]; } else { status = ScGetNewCtrlSetId(idArray, &newId); if(status != NO_ERROR) { SC_LOG0(ERROR, "ScAcceptTheBoot: Could Not Get New Control Set Id.\n"); goto CleanExit; } } // // Accept the boot and save the boot configuration as LKG. // status = RtlNtStatusToDosError(NtInitializeRegistry(REG_INIT_BOOT_ACCEPTED_BASE + (USHORT)newId)); if(status != NO_ERROR) { SC_LOG1(ERROR, "ScAcceptTheBoot: NtInitializeRegistry Failed with %d", status); goto CleanExit; } // // Make this control set the LastKnownGood Control Set. // This is the ControlSet that we last booted from. // if(newId != idArray[LKG_ID]) { // // We only need to do anything if we did not overwrite the old LKG // with NtInitializeRegistry. // idArray[LKG_ID] = newId; status = ScRegSetValueExW( selectKey, // hKey LKG_VALUE_NAME, // lpValueName 0, // dwValueTitle (OPTIONAL) REG_DWORD, // dwType (LPBYTE)&(idArray[LKG_ID]), // lpData sizeof(DWORD)); // cbData if (status != NO_ERROR) { SC_LOG1(ERROR,"ScAcceptTheBoot: ScRegSetValueEx (LkgValue) failed %d\n", status); goto CleanExit; } } // // Commit this boot by deleting anything we would undo since previous boot. // status = ScLastGoodFileCleanup(); if (status != NO_ERROR) { SC_LOG1(ERROR,"ScAcceptTheBoot: LastGoodFileCleanup failed %d\n", status); goto CleanExit; } ScGlobalBootAccepted = TRUE; status = NO_ERROR; SC_LOG0(TRACE,"ScAcceptTheBoot: Done\n"); CleanExit: if (systemKey != 0) { ScRegCloseKey(systemKey); } if (selectKey != 0) { ScRegCloseKey(selectKey); } // // Restore privileges for the current thread. // (VOID)ScReleasePrivilege(); return(status); } BOOL SetupInProgress( HKEY SystemKey, PBOOL pfIsOOBESetup OPTIONAL ) /*++ Routine Description: Checks a registry location to determine if Setup is in Progress. \HKEY_LOCAL_MACHINE\System\Setup value=DWORD SystemSetupInProgress The value is cached so that the registry is examined only on the first call to this function. Arguments: SystemKey - open handle to HKEY_LOCAL_MACHINE\System. This is ignored in all except the first call to this function. Return Value: TRUE - If Setup is in progress FALSE - If Setup isn't in progress --*/ { static DWORD TheValue=0xffffffff; // 0=false, 1=true, // 0xffffffff=uninitialized static DWORD IsOOBE; DWORD status=NO_ERROR; DWORD BytesRequired = sizeof(TheValue); HKEY KeyHandle; if (TheValue == 0xffffffff) { // // First call // SC_ASSERT(SystemKey != NULL); TheValue = 0; IsOOBE = 0; status = ScRegOpenKeyExW( SystemKey, SETUP_PROG_KEY, 0L, KEY_READ, &KeyHandle); if (status == NO_ERROR) { // // There are two registry values that may be set here: // // 1. OobeInProgress -- if it exists and is non-zero, // this is an OOBE boot. // // 2. SystemSetupInProgress -- if it exists and is // non-zero AND it's not an OOBE boot, it's // GUI-mode setup. If OOBE's in progress, // don't even bother checking this one (it may // or may not be set depending on whether we're // in retail OOBE or mini-setup OOBE) and return // FALSE from SetupInProgress (along with the // appropriate OOBE value). // status = ScRegQueryValueExW( KeyHandle, REGSTR_VALUE_OOBEINPROGRESS, NULL, NULL, (LPBYTE) &IsOOBE, &BytesRequired); if (IsOOBE != 0) { SC_ASSERT(status == NO_ERROR); IsOOBE = 1; } if (IsOOBE == 0) { status = ScRegQueryValueExW( KeyHandle, SETUP_PROG_VALUE_NAME, NULL, NULL, (LPBYTE) &TheValue, &BytesRequired); if (TheValue != 0) { SC_ASSERT(status == NO_ERROR); TheValue = 1; } } ScRegCloseKey(KeyHandle); } } SC_LOG(TRACE,"SetupInProgress = %d (0=FALSE,else TRUE)\n",TheValue); if (pfIsOOBESetup) { SC_LOG(TRACE, "SetupInProgress: IsOOBE = %d (0=FALSE,else TRUE)\n", IsOOBE); *pfIsOOBESetup = IsOOBE; } return TheValue; }