windows-nt/Source/XPSP1/NT/base/screg/sc/server/bootcfg.cxx
2020-09-26 16:20:57 +08:00

2772 lines
74 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
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 <stdlib.h> // ultoa
#include "scsec.h" // ScAccessValidate()
#include "bootcfg.h" // ScRegDeleteTree()
#include "scconfig.h" // ScOpenServicesKey()
#include <svcslib.h> // SetupInProgress()
#include <ntsetup.h> // REGSTR_VALUE_OOBEINPROGRESS
#include <bootstatus.h>
//
// 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<NumberOfValues; i++) {
ValueNameLength = MaxValueNameLength;
DataLength = MaxValueDataLength;
status = ScRegEnumValueW (
SourceKey,
i,
ValueName,
&ValueNameLength,
NULL,
&Type,
DataPtr,
&DataLength);
if (status != NO_ERROR) {
SC_LOG1(ERROR,"ScCopyKeyValues,ScRegEnumValue Failed %d\n",status);
break;
}
status = ScRegSetValueExW (
DestKey,
ValueName,
0L,
Type,
DataPtr,
DataLength);
if (status != NO_ERROR) {
SC_LOG1(ERROR,"ScCopyKeyValues,ScRegSetValueEx Failed %d\n",status);
break;
}
} // end-for
LocalFree(DataPtr);
LocalFree(ValueName);
return;
}
#endif
VOID
ScDeleteRegServiceEntry(
LPWSTR ServiceName
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
DWORD status;
HKEY parentKey;
HKEY keyToDelete;
ULONG privileges[4];
LPWSTR ServicesKeyPath = SERVICES_KEY;
//*******************************
// Delete the registry node for
// This service.
//*******************************
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, "ScDeleteRegServiceEntry: ScGetPrivilege Failed %d\n",
status);
return;
}
//
// Open the "services" section of the CurrentControlSet.
//
status = ScRegOpenKeyExW(
HKEY_LOCAL_MACHINE, // hKey
ServicesKeyPath, // lpSubKey
0L, // ulOptions (reserved)
SC_DELETE_KEY_ACCESS, // desired access
&parentKey); // Newly Opened Key Handle
if (status != NO_ERROR) {
SC_LOG2(ERROR,"ScDeleteRegServiceEntry: "
"ScRegOpenKeyEx (%ws) failed %d\n",ServicesKeyPath,
status);
//
// Restore privileges for the current thread.
//
(VOID)ScReleasePrivilege();
return;
}
//
// Get Key for the Tree we are to delete
//
status = ScRegOpenKeyExW(
parentKey, // hKey
ServiceName, // lpSubKey
0L, // ulOptions (reserved)
SC_DELETE_KEY_ACCESS, // desired access
&keyToDelete); // Newly Opened Key Handle
if (status != NO_ERROR) {
SC_LOG2(ERROR,"ScDeleteRegServiceEntry: "
"ScRegOpenKeyEx (%ws) failed %d\n",ServiceName,
status);
ScRegCloseKey(parentKey);
//
// Restore privileges for the current thread.
//
(VOID)ScReleasePrivilege();
return;
}
//
// Delete the Key.
// NOTE: ScDeleteRegTree will also close the handle to the keyToDelete.
//
ScDeleteRegTree(parentKey, keyToDelete, ServiceName);
ScRegCloseKey(parentKey);
//
// Restore privileges for the current thread.
//
(VOID)ScReleasePrivilege();
return;
}
VOID
ScGatherOrphanIds(
HKEY SystemKey,
LPDWORD *OrphanIdPtr,
LPDWORD idArray
)
/*++
Routine Description:
This function searches through the system key to find any orphan control
set ids. If any are found, they are packed into an array of ids that
are passed back to the caller.
NOTE: This function allocates memory for *OrphanIdPtr if orphans
exist. It is the responsibility of the caller to free this memory.
Arguments:
SystemKey - This is an open handle to the system key.
OrphanIdPtr - This is a pointer to a location for the pointer to
the array of Orphan IDs. If there are no orphans, then this pointer
is NULL on return from this routine.
idArray - This is the array of IDs that are used in the select key.
Return Value:
Note:
--*/
{
DWORD enumStatus;
DWORD status;
WCHAR KeyName[ MAX_PATH ];
DWORD KeyNameLength = MAX_PATH;
DWORD i=0;
DWORD j=0;
DWORD numOrphans=0;
DWORD num;
LPDWORD tempIdArray;
DWORD matchInArray;
WCHAR ClassName[ MAX_PATH ];
DWORD ClassNameLength=MAX_PATH;
DWORD NumberOfSubKeys;
DWORD MaxSubKeyLength;
DWORD MaxClassLength;
DWORD NumberOfValues;
DWORD MaxValueNameLength;
DWORD MaxValueDataLength;
DWORD SecurityDescriptorLength;
FILETIME LastWriteTime;
//
// If the pointer points to something - free it. and make the pointer
// NULL.
//
LocalFree(*OrphanIdPtr);
*OrphanIdPtr = NULL;
//
// Find out how many subkeys there are in the system key.
// This will tell us the maximum size of array required to store
// potential orphan control set IDs.
//
status = ScRegQueryInfoKeyW(
SystemKey,
ClassName,
&ClassNameLength,
NULL,
&NumberOfSubKeys,
&MaxSubKeyLength,
&MaxClassLength,
&NumberOfValues,
&MaxValueNameLength,
&MaxValueDataLength,
&SecurityDescriptorLength,
&LastWriteTime
);
if (status != NO_ERROR) {
SC_LOG1(ERROR, "ScGatherOrphanIds: ScRegQueryInfoKey Failed %d\n",status);
return;
}
//
// Allocate a buffer for the orphan control set IDs. This buffer is
// initialized to 0 to guanantee that the array if IDs will be terminated
// by a 0.
//
tempIdArray = (LPDWORD)LocalAlloc(LMEM_ZEROINIT, sizeof(DWORD) * (NumberOfSubKeys+1));
if (tempIdArray == NULL) {
SC_LOG0(ERROR, "ScGatherOrphanIds:LocalAlloc Failed\n");
}
do {
enumStatus = ScRegEnumKeyW(
SystemKey,
i,
KeyName,
KeyNameLength);
if (enumStatus == NO_ERROR) {
//
// We have a key name, is it a control set?
//
if ((wcslen(KeyName) == (CTRL_SET_NAME_CHAR_COUNT)) &&
(!wcsncmp(
CTRL_SET_NAME_TEMPLATE,
KeyName,
CTRL_SET_NAME_NUM_OFFSET))) {
//
// It appears to be a control set, now get the number
// and see if it is in the array of ids from the select
// key.
//
num = (DWORD)_wtol(KeyName+CTRL_SET_NAME_NUM_OFFSET);
matchInArray = FALSE;
for (j=0; j<NUM_IDS; j++) {
if (num == idArray[j]) {
matchInArray = TRUE;
break;
}
}
//
// It's not in the array of ids from the select key.
// Add it to the number of orphans.
//
if ((matchInArray == FALSE) && (num < 1000)) {
if (numOrphans < NumberOfSubKeys) {
tempIdArray[numOrphans] = num;
numOrphans++;
}
}
}
}
i++;
}
while (enumStatus == NO_ERROR);
if (numOrphans > 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;
}