2025-04-27 07:49:33 -04:00

532 lines
18 KiB
C

/****************************** Module Header ******************************\
* Module Name: reason.c
*
* Copyright (c) 1985 - 2000, Microsoft Corporation
*
* This module contains the (private) APIs for the shutdown reason stuff.
*
* History:
* ??-??-???? HughLeat Wrote it as part of msgina.dll
* 11-15-2000 JasonSch Moved from msgina.dll to its new, temporary home in
* user32.dll. Ultimately this code should live in
* advapi32.dll, but that's contingent upon LoadString
* being moved to ntdll.dll.
\***************************************************************************/
#include "precomp.h"
#pragma hdrstop
#include <regstr.h>
REASON_INITIALISER g_rgReasonInits[] = {
{ UCLEANUI | SHTDN_REASON_MAJOR_HARDWARE | SHTDN_REASON_MINOR_MAINTENANCE, IDS_REASON_UNPLANNED_HARDWARE_MAINTENANCE_TITLE, IDS_REASON_HARDWARE_MAINTENANCE_DESCRIPTION },
{ PCLEANUI | SHTDN_REASON_MAJOR_HARDWARE | SHTDN_REASON_MINOR_MAINTENANCE, IDS_REASON_PLANNED_HARDWARE_MAINTENANCE_TITLE, IDS_REASON_HARDWARE_MAINTENANCE_DESCRIPTION },
{ UCLEANUI | SHTDN_REASON_MAJOR_HARDWARE | SHTDN_REASON_MINOR_INSTALLATION, IDS_REASON_UNPLANNED_HARDWARE_INSTALLATION_TITLE, IDS_REASON_HARDWARE_INSTALLATION_DESCRIPTION },
{ PCLEANUI | SHTDN_REASON_MAJOR_HARDWARE | SHTDN_REASON_MINOR_INSTALLATION, IDS_REASON_PLANNED_HARDWARE_INSTALLATION_TITLE, IDS_REASON_HARDWARE_INSTALLATION_DESCRIPTION },
{ UCLEANUI | SHTDN_REASON_MAJOR_OPERATINGSYSTEM | SHTDN_REASON_MINOR_UPGRADE, IDS_REASON_UNPLANNED_OPERATINGSYSTEM_UPGRADE_TITLE, IDS_REASON_OPERATINGSYSTEM_UPGRADE_DESCRIPTION },
{ PCLEANUI | SHTDN_REASON_MAJOR_OPERATINGSYSTEM | SHTDN_REASON_MINOR_UPGRADE, IDS_REASON_PLANNED_OPERATINGSYSTEM_UPGRADE_TITLE, IDS_REASON_OPERATINGSYSTEM_UPGRADE_DESCRIPTION },
{ UCLEANUI | SHTDN_REASON_MAJOR_OPERATINGSYSTEM | SHTDN_REASON_MINOR_RECONFIG, IDS_REASON_UNPLANNED_OPERATINGSYSTEM_RECONFIG_TITLE, IDS_REASON_OPERATINGSYSTEM_RECONFIG_DESCRIPTION },
{ PCLEANUI | SHTDN_REASON_MAJOR_OPERATINGSYSTEM | SHTDN_REASON_MINOR_RECONFIG, IDS_REASON_PLANNED_OPERATINGSYSTEM_RECONFIG_TITLE, IDS_REASON_OPERATINGSYSTEM_RECONFIG_DESCRIPTION },
{ UCLEANUI | SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_HUNG, IDS_REASON_APPLICATION_HUNG_TITLE, IDS_REASON_APPLICATION_HUNG_DESCRIPTION },
{ UCLEANUI | SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_UNSTABLE, IDS_REASON_APPLICATION_UNSTABLE_TITLE, IDS_REASON_APPLICATION_UNSTABLE_DESCRIPTION },
{ UCLEANUI | SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_MAINTENANCE, IDS_REASON_APPLICATION_MAINTENANCE_TITLE, IDS_REASON_APPLICATION_MAINTENANCE_DESCRIPTION },
{ PCLEANUI | SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_MAINTENANCE, IDS_REASON_APPLICATION_PM_TITLE, IDS_REASON_APPLICATION_PM_DESCRIPTION },
{ UCLEANUI | SHTDN_REASON_FLAG_COMMENT_REQUIRED | SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER, IDS_REASON_UNPLANNED_OTHER_TITLE, IDS_REASON_OTHER_DESCRIPTION },
{ PCLEANUI | SHTDN_REASON_FLAG_COMMENT_REQUIRED | SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER, IDS_REASON_PLANNED_OTHER_TITLE, IDS_REASON_OTHER_DESCRIPTION },
{ UDIRTYUI | SHTDN_REASON_MAJOR_SYSTEM | SHTDN_REASON_MINOR_BLUESCREEN, IDS_REASON_SYSTEMFAILURE_BLUESCREEN_TITLE, IDS_REASON_SYSTEMFAILURE_BLUESCREEN_DESCRIPTION },
{ UDIRTYUI | SHTDN_REASON_MAJOR_POWER | SHTDN_REASON_MINOR_CORDUNPLUGGED, IDS_REASON_POWERFAILURE_CORDUNPLUGGED_TITLE, IDS_REASON_POWERFAILURE_CORDUNPLUGGED_DESCRIPTION },
{ UDIRTYUI | SHTDN_REASON_MAJOR_POWER | SHTDN_REASON_MINOR_ENVIRONMENT, IDS_REASON_POWERFAILURE_ENVIRONMENT_TITLE, IDS_REASON_POWERFAILURE_ENVIRONMENT_DESCRIPTION },
{ UDIRTYUI | SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_HUNG, IDS_REASON_OTHERFAILURE_HUNG_TITLE, IDS_REASON_OTHERFAILURE_HUNG_DESCRIPTION },
{ UDIRTYUI | SHTDN_REASON_FLAG_COMMENT_REQUIRED | SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER, IDS_REASON_OTHERFAILURE_TITLE, IDS_REASON_OTHER_DESCRIPTION },
};
BOOL ReasonCodeNeedsComment(DWORD dwCode)
{
return (dwCode & SHTDN_REASON_FLAG_COMMENT_REQUIRED) != 0;
}
BOOL ReasonCodeNeedsBugID(DWORD dwCode)
{
return (dwCode & SHTDN_REASON_FLAG_DIRTY_PROBLEM_ID_REQUIRED) != 0;
}
/*
* Here is the regular expression used to parse the user defined reason codes
* in the registry:
*
* S -> 's' | 'S' { Set For Clean UI }
* D -> 'd' | 'D' { Set For Dirty UI }
* P -> 'p' | 'P' { Set Planned }
* C -> 'c' | 'C' { Set Comment Required }
* B -> 'b' | 'B' { Set Problem Id Required in Dirty Mode }
*
* WS -> ( ' ' | '\t' | '\n' )*
*
* Delim -> ';' | ',' | ':'
* Flag -> S | D | P | C | B
* Flags -> ( WS . Flag . WS )*
*
* Digit -> '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | ''8' | '9'
* Num -> WS . Digit* . WS
* Maj -> Num { Set Major Code }
* Min -> Num { Set Minor Code }
*
* ValidSentence -> Flags . Delim . Maj . Delim . Min . Delim |
* Flags . Delim . Maj . Delim . Min |
* Flags . Delim . Maj |
* Flags
*
* All initial states are false for each flag and 0 for minor and major reason
* codes.
* If neither S nor D are specified (which makes it inaccessible) then both
* are specified.
*/
BOOL
ParseReasonCode(
PWCHAR lpString,
LPDWORD lpdwCode)
{
WCHAR c;
UINT major = 0, minor = 0;
*lpdwCode = SHTDN_REASON_FLAG_USER_DEFINED;
// Read the flags part.
c = *lpString;
while( c != 0 && c != L';' && c != L':' && c != L',' ) {
switch( c ) {
case L'P' : case L'p' :
*lpdwCode |= SHTDN_REASON_FLAG_PLANNED;
break;
case L'C' : case L'c' :
*lpdwCode |= SHTDN_REASON_FLAG_COMMENT_REQUIRED;
break;
case L'S' : case L's' :
*lpdwCode |= SHTDN_REASON_FLAG_CLEAN_UI;
break;
case L'D' : case L'd' :
*lpdwCode |= SHTDN_REASON_FLAG_DIRTY_UI;
break;
case L'B' : case L'b' :
*lpdwCode |= SHTDN_REASON_FLAG_DIRTY_PROBLEM_ID_REQUIRED;
break;
case L' ' : case L'\t' : case L'\n' :
break;
default : return FALSE;
}
c = *++lpString;
}
// If neither CLEAN_UI nor DIRTY_UI are set, set both. Otherwise this
// reason is useless.
if ((*lpdwCode & ( SHTDN_REASON_FLAG_CLEAN_UI | SHTDN_REASON_FLAG_DIRTY_UI)) == 0) {
*lpdwCode |= (SHTDN_REASON_FLAG_CLEAN_UI | SHTDN_REASON_FLAG_DIRTY_UI);
}
if (c == 0) {
// Major Reason = NONE
// Minor Reason = NONE
return TRUE;
}
c = *++lpString; // Skip delimiter.
// Eat WS and padded 0s
while(c == L' ' || c == L'\t' || c == L'\n' || c == L'0') {
c = *++lpString;
}
// Parse major reason
while(c != 0 && c != L';' && c != L':' && c != L',' && c != L' ' && c != L'\t' && c != L'\n') {
if (c < L'0' || c > L'9') {
return FALSE;
}
major = major * 10 + c - L'0';
c = *++lpString;
}
if (major > 0xff) {
return FALSE;
}
*lpdwCode |= major << 16;
// Eat WS
while(c == L' ' || c == L'\t' || c == L'\n') {
c = *++lpString;
}
if (c == 0) {
// Minor Reason = NONE
return TRUE;
}
// Should have a delimiter
if (c != L';' && c != L':' && c != L',') {
return FALSE;
}
c = *++lpString; // Skip delimiter.
// Eat WS and padded 0s
while(c == L' ' || c == L'\t' || c == L'\n' || c == L'0') {
c = *++lpString;
}
// Parse minor reason
while(c != 0 && c != L';' && c != L':' && c != L',' && c != L' ' && c != L'\t' && c != L'\n') {
if (c < L'0' || c > L'9') {
return FALSE;
}
minor = minor * 10 + c - L'0';
c = *++lpString;
}
if (minor > 0xffff) {
return FALSE;
}
*lpdwCode |= minor;
// Skip white stuff to the end
while(c == L' ' || c == L'\t' || c == L'\n' || c == L';' || c == L':' || c == L',') {
c = *++lpString;
}
// This char had better be the null char
return (c == 0);
}
int
__cdecl
CompareReasons(
CONST VOID *A,
CONST VOID *B)
{
REASON *a = *(REASON **)A;
REASON *b = *(REASON **)B;
// Shift the planned bit out and put it back in the bottom.
// Ignore all ui bits.
DWORD dwA = ((a->dwCode & SHTDN_REASON_VALID_BIT_MASK ) << 1) + !!(a->dwCode & SHTDN_REASON_FLAG_PLANNED);
DWORD dwB = ((b->dwCode & SHTDN_REASON_VALID_BIT_MASK ) << 1) + !!(b->dwCode & SHTDN_REASON_FLAG_PLANNED);
if (dwA < dwB) {
return -1;
} else if (dwA == dwB) {
return 0;
} else {
return 1;
}
}
BOOL
SortReasonArray(
REASONDATA *pdata)
{
qsort(pdata->rgReasons, pdata->cReasons, sizeof(REASON *), CompareReasons);
return TRUE;
}
BOOL
AppendReason(
REASONDATA *pdata,
REASON *reason)
{
int i;
// Insert the new reason into the list.
if (pdata->cReasons < pdata->cReasonCapacity) {
pdata->rgReasons[pdata->cReasons++] = reason;
} else {
// Need to expand the list.
REASON **temp_list = (REASON **)UserLocalAlloc(0, sizeof(REASON *) * pdata->cReasonCapacity * 2);
if (temp_list == NULL) {
return FALSE;
}
for (i = 0; i < pdata->cReasons; ++i) {
temp_list[i] = pdata->rgReasons[i];
}
temp_list[pdata->cReasons++] = reason;
pdata->cReasonCapacity *= 2;
if (pdata->rgReasons ) {
UserLocalFree(pdata->rgReasons);
}
pdata->rgReasons = temp_list;
}
return TRUE;
}
BOOL
LoadReasonStrings(
int idStringName,
int idStringDesc,
REASON *reason)
{
BOOL fSuccess = TRUE;
fSuccess &= (LoadStringW(hmodUser, idStringName, reason->szName, ARRAYSIZE(reason->szName)) != 0);
fSuccess &= (LoadStringW(hmodUser, idStringDesc, reason->szDesc, ARRAYSIZE(reason->szDesc)) != 0);
return fSuccess;
}
BOOL
BuildPredefinedReasonArray(
REASONDATA *pdata,
BOOL forCleanUI,
BOOL forDirtyUI)
{
int i;
DWORD code;
if (!forCleanUI && !forDirtyUI) {
return TRUE;
}
for (i = 0; i < ARRAYSIZE(g_rgReasonInits); ++i) {
REASON *temp_reason = NULL;
code = g_rgReasonInits[ i ].dwCode;
if ((forCleanUI && (code & SHTDN_REASON_FLAG_CLEAN_UI)) ||
(forDirtyUI && (code & SHTDN_REASON_FLAG_DIRTY_UI))) {
temp_reason = (REASON *)UserLocalAlloc(0, sizeof(REASON));
if (temp_reason == NULL) {
return FALSE;
}
temp_reason->dwCode = g_rgReasonInits[i].dwCode;
if (!LoadReasonStrings(g_rgReasonInits[i].dwNameId, g_rgReasonInits[i].dwDescId, temp_reason)) {
UserLocalFree(temp_reason);
return FALSE;
}
if (!AppendReason(pdata, temp_reason)) {
UserLocalFree(temp_reason);
return FALSE;
}
}
}
return TRUE;
}
BOOL
BuildUserDefinedReasonArray(
REASONDATA *pdata,
HKEY hReliabilityKey,
BOOL forCleanUI,
BOOL forDirtyUI
)
{
UINT i;
HKEY hKey = NULL;
DWORD num_values;
DWORD max_value_len;
DWORD rc;
if (!forCleanUI && !forDirtyUI) {
return TRUE;
}
// Open the user defined key.
rc = RegCreateKeyEx(hReliabilityKey,
TEXT("UserDefined"),
0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
NULL, &hKey, NULL);
if (rc != ERROR_SUCCESS) {
goto fail;
}
rc = RegQueryInfoKeyW(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &num_values, NULL, &max_value_len, NULL, NULL);
if (rc != ERROR_SUCCESS) {
goto fail;
}
// Read the user defined reasons.
for (i = 0; i < num_values; ++i) {
WCHAR name_buffer[ 256 ]; // No value or key can have a longer name.
DWORD name_buffer_len = 256;
DWORD type;
WCHAR data[MAX_REASON_NAME_LEN + MAX_REASON_DESC_LEN + 3]; // Space for name, desc and three null chars.
WCHAR *buf = data;
DWORD data_len = (MAX_REASON_NAME_LEN + MAX_REASON_DESC_LEN + 3) * sizeof(WCHAR);
DWORD code;
REASON *temp_reason = NULL;
rc = RegEnumValueW(hKey, i, name_buffer, &name_buffer_len, NULL, &type, (LPBYTE)data, &data_len);
if (rc != ERROR_SUCCESS && rc != ERROR_MORE_DATA) {
continue;
}
if (type != REG_MULTI_SZ) {
continue; // Not a multi_string - ignore it.
}
// Parse the code.
if (!ParseReasonCode(name_buffer, &code)) {
continue;
}
if ((forCleanUI && (code & SHTDN_REASON_FLAG_CLEAN_UI) != 0) ||
(forDirtyUI && (code & SHTDN_REASON_FLAG_DIRTY_UI) != 0)) {
if (rc == ERROR_MORE_DATA) { // Multi string too long.
// Allocate a buffer of the right size.
buf = (WCHAR *)UserLocalAlloc(0, data_len);
if (buf == 0) {
goto fail;
}
rc = (DWORD)RegEnumValueW(hKey, i, name_buffer, &name_buffer_len, NULL, &type, (LPBYTE)buf, &data_len);
if (rc != ERROR_SUCCESS) {
UserLocalFree(buf);
continue;
}
}
// Allocate a new reason
temp_reason = (REASON *)UserLocalAlloc(LPTR, sizeof(REASON));
if (temp_reason == NULL) {
if (buf != data) {
UserLocalFree(buf);
}
goto fail;
}
// Copy the stuff over.
temp_reason->dwCode = code;
lstrcpynW(temp_reason->szName, buf, MAX_REASON_NAME_LEN);
temp_reason->szName[MAX_REASON_NAME_LEN] = 0;
lstrcpynW(temp_reason->szDesc, buf + wcslen(buf) + 1, MAX_REASON_DESC_LEN);
temp_reason->szDesc[MAX_REASON_DESC_LEN] = 0;
if (buf != data) {
UserLocalFree(buf);
}
if (!AppendReason(pdata, temp_reason)) {
UserLocalFree(temp_reason);
goto fail;
}
}
}
RegCloseKey(hKey);
return TRUE;
fail :
if (hKey != NULL) {
RegCloseKey(hKey);
}
return FALSE;
}
BOOL
BuildReasonArray(
REASONDATA *pdata,
BOOL forCleanUI,
BOOL forDirtyUI)
{
HKEY hReliabilityKey;
DWORD ignore_predefined_reasons = FALSE;
DWORD value_size = sizeof(DWORD);
DWORD rc;
HANDLE hEventLog = RegisterEventSourceW(NULL, L"USER32");
if (hEventLog == NULL) {
return FALSE;
}
pdata->rgReasons = (REASON **)UserLocalAlloc(0, sizeof(REASON *) * ARRAYSIZE(g_rgReasonInits));
if (pdata->rgReasons == NULL) {
return FALSE;
}
pdata->cReasonCapacity = ARRAYSIZE(g_rgReasonInits);
pdata->cReasons = 0;
// Open the reliability key.
rc = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
REGSTR_PATH_RELIABILITY,
0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
&hReliabilityKey, NULL);
if (rc == ERROR_SUCCESS) {
rc = RegQueryValueEx(hReliabilityKey, REGSTR_VAL_SHUTDOWN_IGNORE_PREDEFINED, NULL, NULL, (UCHAR *)&ignore_predefined_reasons, &value_size);
if (rc != ERROR_SUCCESS) {
ignore_predefined_reasons = FALSE;
}
if (!BuildUserDefinedReasonArray(pdata, hReliabilityKey, forCleanUI, forDirtyUI) || pdata->cReasons == 0) {
ignore_predefined_reasons = FALSE;
}
RegCloseKey(hReliabilityKey);
}
if (!ignore_predefined_reasons) {
if (!BuildPredefinedReasonArray(pdata, forCleanUI, forDirtyUI)) {
return FALSE;
}
}
return SortReasonArray(pdata);
}
VOID
DestroyReasons(
REASONDATA *pdata)
{
int i;
if (pdata->rgReasons != 0) {
for (i = 0; i < pdata->cReasons; ++i) {
UserLocalFree( pdata->rgReasons[i]);
}
UserLocalFree(pdata->rgReasons);
pdata->rgReasons = 0;
}
}
/*
* Get the title from the reason code.
* Returns FALSE on error, TRUE otherwise.
*
* If the reason code cannot be found, then it fills the title with a default
* string.
*/
BOOL
GetReasonTitleFromReasonCode(
DWORD code,
WCHAR *lpTitle,
DWORD dwTitleLen)
{
REASONDATA data;
int i;
if (lpTitle == NULL || dwTitleLen == 0) {
return FALSE;
}
// Load the reasons.
if (BuildReasonArray(&data, TRUE, TRUE) == FALSE) {
return FALSE;
}
// Try to find the reason.
for (i = 0; i < data.cReasons; ++i) {
if ((code & SHTDN_REASON_VALID_BIT_MASK) ==
(data.rgReasons[i]->dwCode & SHTDN_REASON_VALID_BIT_MASK)) {
lstrcpynW(lpTitle, data.rgReasons[i]->szName, dwTitleLen);
DestroyReasons(&data);
return TRUE;
}
}
// Reason not found. Load the default string and return that.
DestroyReasons(&data);
return (LoadStringW(hmodUser, IDS_REASON_DEFAULT_TITLE, lpTitle, dwTitleLen) != 0);
}