Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1196 lines
30 KiB

//
// Enable driver verifier support for ntoskrnl
// Copyright (c) Microsoft Corporation, 1999
//
//
// module: regutil.cxx
// author: DMihai
// created: 04/19/99
// description: registry keys manipulation routines
//
extern "C" {
#include "nt.h"
#include "ntrtl.h"
#include "nturtl.h"
}
#include <windows.h>
#include <tchar.h>
#include <stdlib.h>
#include "ResId.hxx"
#include "RegUtil.hxx"
#include "GenUtil.hxx"
#define VRF_MAX_DRIVER_STRING_LENGTH 4196
#define LEVEL2_IO_VERIFIER_ENABLED_VALUE 3
//////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////// Registry Strings
//////////////////////////////////////////////////////////////////////
LPCTSTR RegMemoryManagementKeyName =
TEXT ("System\\CurrentControlSet\\Control\\Session Manager\\Memory Management");
LPCTSTR RegVerifyDriversValueName =
TEXT ("VerifyDrivers");
LPCTSTR RegVerifyDriverLevelValueName =
TEXT ("VerifyDriverLevel");
LPCTSTR RegIOVerifyKeyName =
TEXT ("System\\CurrentControlSet\\Control\\Session Manager\\I/O System");
LPCTSTR RegIOVerifySubKeyName =
TEXT ("I/O System");
LPCTSTR RegIOVerifyLevelValueName =
TEXT ("IoVerifierLevel");
LPCTSTR RegSessionManagerKeyName =
TEXT ("System\\CurrentControlSet\\Control\\Session Manager");
//////////////////////////////////////////////////////////////////////
/////////////// Forward decl for local registry manipulation functions
//////////////////////////////////////////////////////////////////////
BOOL
ReadRegistryValue (
HKEY HKey,
LPCTSTR Name,
DWORD * Value);
BOOL
WriteRegistryValue (
HKEY MmKey,
LPCTSTR Name,
DWORD Value);
BOOL
ReadMmString (
HKEY MmKey,
LPCTSTR Name,
LPTSTR Value);
BOOL
WriteMmString (
HKEY MmKey,
LPCTSTR Name,
LPTSTR Value);
//////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////// Public functions
//////////////////////////////////////////////////////////////////////
void
WriteVerifierKeys(
BOOL bEnableKrnVerifier,
DWORD dwNewVerifierFlags,
DWORD dwNewIoLevel,
TCHAR *strKernelModuleName )
{
HKEY MmKey = NULL;
DWORD dwExitCode;
DWORD dwCrtFlags;
DWORD dwCrtIoLevel;
BOOL bMustAppendName;
BOOL bAlreadyInRegistry;
LONG lOpenResult;
int nKernelModuleNameLen;
int nStringLen;
TCHAR *pstrCrtNameMatch, *pstrSubstring, *pCrtChar;
TCHAR strVrfDriver [VRF_MAX_DRIVER_STRING_LENGTH];
TCHAR strVrfDriverNew [VRF_MAX_DRIVER_STRING_LENGTH];
dwExitCode = EXIT_CODE_NOTHING_CHANGED;
//
// Open the Mm key
//
lOpenResult = RegOpenKeyEx (
HKEY_LOCAL_MACHINE,
RegMemoryManagementKeyName,
0,
KEY_QUERY_VALUE | KEY_WRITE,
&MmKey);
if (lOpenResult != ERROR_SUCCESS)
{
//
// fatal error
//
dwExitCode = EXIT_CODE_ERROR;
if( lOpenResult == ERROR_ACCESS_DENIED )
{
DisplayMessage( IDS_ACCESS_IS_DENIED );
}
else
{
DisplayMessage(
IDS_REGOPENKEYEX_FAILED,
RegMemoryManagementKeyName,
(DWORD)lOpenResult);
}
}
if( dwExitCode != EXIT_CODE_ERROR != 0 )
{
//
// the IO verifier will be enabled
//
if( dwNewIoLevel != 2 )
{
//
// only levels 1 & 2 are supported
//
dwNewIoLevel = 1;
}
//
// get the current IO level
//
if( GetIoVerificationLevel( &dwCrtIoLevel ) == FALSE )
{
//
// fatal error
//
dwExitCode = EXIT_CODE_ERROR;
}
}
if( dwExitCode != EXIT_CODE_ERROR )
{
if( ReadRegistryValue( MmKey, RegVerifyDriverLevelValueName, &dwCrtFlags ) == FALSE )
{
dwExitCode = EXIT_CODE_ERROR;
}
else
{
if( dwNewVerifierFlags != -1 )
{
//
// have some new flags
//
//
// modify the flags in registry
//
if( dwCrtFlags != dwNewVerifierFlags )
{
if( WriteRegistryValue( MmKey, RegVerifyDriverLevelValueName, dwNewVerifierFlags ) == FALSE )
{
dwExitCode = EXIT_CODE_ERROR;
}
else
{
dwExitCode = EXIT_CODE_REBOOT;
}
}
if( dwExitCode != EXIT_CODE_ERROR )
{
if( ( dwNewVerifierFlags & DRIVER_VERIFIER_IO_CHECKING ) == 0 )
{
//
// IO verification is not enabled - disable "level 2" value too
//
dwNewIoLevel = 1;
}
//
// the IO verifier will be enabled
//
if( dwCrtIoLevel != dwNewIoLevel )
{
//
// need to switch the IO verification level
//
if( SwitchIoVerificationLevel( dwNewIoLevel ) == TRUE )
{
dwExitCode = EXIT_CODE_REBOOT;
}
else
{
dwExitCode = EXIT_CODE_ERROR;
}
}
}
}
}
if( dwExitCode != EXIT_CODE_ERROR && bEnableKrnVerifier )
{
//
// enable verifier for the kernel
//
if( ReadMmString (MmKey, RegVerifyDriversValueName, strVrfDriver) == FALSE)
{
dwExitCode = EXIT_CODE_ERROR;
}
else
{
bAlreadyInRegistry = IsModuleNameAlreadyInRegistry(
strKernelModuleName,
strVrfDriver );
if( bAlreadyInRegistry == FALSE )
{
_tcscpy( strVrfDriverNew, strKernelModuleName );
if( strVrfDriver[ 0 ] != (TCHAR)0 )
{
if( strVrfDriver[ 0 ] != _T( ' ' ) &&
strVrfDriver[ 0 ] != _T( '\t' ) )
{
//
// add a space first
//
_tcscat( strVrfDriverNew, _T( " " ) );
}
//
// add the old verified drivers at the end
//
_tcscat( strVrfDriverNew, strVrfDriver );
}
//
// write the value
//
if (WriteMmString (MmKey, RegVerifyDriversValueName, strVrfDriverNew) == FALSE)
{
dwExitCode = EXIT_CODE_ERROR;
}
else
{
dwExitCode = EXIT_CODE_REBOOT;
}
}
}
}
RegCloseKey (MmKey);
}
if( EXIT_CODE_REBOOT == dwExitCode )
{
DisplayMessage( IDS_MUST_REBOOT );
}
else
{
if( EXIT_CODE_NOTHING_CHANGED == dwExitCode )
{
DisplayMessage( IDS_NOTHING_CHANGED );
}
}
exit( dwExitCode );
}
///////////////////////////////////////////////////////////////////
void
RemoveModuleNameFromRegistry(
TCHAR *strKernelModuleName )
{
HKEY MmKey = NULL;
DWORD dwExitCode;
LONG lOpenResult;
int nKernelModuleNameLen;
int nStringLen;
int nLeftToCopy;
TCHAR *pstrCrtNameMatch, *pstrSubstring;
TCHAR strVrfDriver [VRF_MAX_DRIVER_STRING_LENGTH];
TCHAR strVrfDriverNew [VRF_MAX_DRIVER_STRING_LENGTH];
dwExitCode = EXIT_CODE_NOTHING_CHANGED;
//
// Open the Mm key
//
lOpenResult = RegOpenKeyEx (
HKEY_LOCAL_MACHINE,
RegMemoryManagementKeyName,
0,
KEY_QUERY_VALUE | KEY_WRITE,
&MmKey);
if (lOpenResult != ERROR_SUCCESS)
{
dwExitCode = EXIT_CODE_ERROR;
if( lOpenResult == ERROR_ACCESS_DENIED )
{
DisplayMessage( IDS_ACCESS_IS_DENIED );
}
else
{
DisplayMessage(
IDS_REGOPENKEYEX_FAILED,
RegMemoryManagementKeyName,
(DWORD)lOpenResult);
}
}
else
{
if( ReadMmString (MmKey, RegVerifyDriversValueName, strVrfDriver) == FALSE)
{
dwExitCode = EXIT_CODE_ERROR;
}
else
{
pstrCrtNameMatch = strVrfDriver;
do
{
pstrSubstring = _tcsstr( pstrCrtNameMatch, strKernelModuleName );
if( pstrSubstring != NULL )
{
//
// the name seems to be there
//
nKernelModuleNameLen = _tcsclen( strKernelModuleName );
nStringLen = _tcsclen( pstrSubstring );
if( nStringLen > nKernelModuleNameLen &&
pstrSubstring[ nKernelModuleNameLen ] != _TEXT(' ') &&
pstrSubstring[ nKernelModuleNameLen ] != _TEXT('\t') )
{
//
// this is not our name, continue searching
//
pstrCrtNameMatch += nKernelModuleNameLen;
}
else
{
if( pstrSubstring != &strVrfDriver[ 0 ] &&
(* (pstrSubstring - 1) ) != _TEXT(' ') &&
(* (pstrSubstring - 1) ) != _TEXT('\t') )
{
//
// this is not our name, continue searching
//
pstrCrtNameMatch += min( nKernelModuleNameLen, nStringLen );
}
else
{
//
// kernel's module name is in the registry
//
strVrfDriverNew[0] = (TCHAR)0;
_tcsncat(
strVrfDriverNew,
strVrfDriver,
(size_t)(pstrSubstring - &strVrfDriver[0]) );
nLeftToCopy = nStringLen - nKernelModuleNameLen;
pstrSubstring += nKernelModuleNameLen;
while( nLeftToCopy > 0 )
{
if( *pstrSubstring != _TEXT( ' ' ) &&
*pstrSubstring != _TEXT( '\t' ) )
{
//
// append what starts from here
//
_tcscat( strVrfDriverNew, pstrSubstring );
break;
}
else
{
//
// skip spaces
//
pstrSubstring ++;
nLeftToCopy --;
}
}
//
// write the new value to the registry
//
if (WriteMmString (MmKey, RegVerifyDriversValueName, strVrfDriverNew) == FALSE)
{
dwExitCode = EXIT_CODE_ERROR;
}
else
{
dwExitCode = EXIT_CODE_REBOOT;
}
break;
}
}
}
}
while( pstrSubstring != NULL );
}
RegCloseKey (MmKey);
}
if( EXIT_CODE_REBOOT == dwExitCode )
{
DisplayMessage( IDS_MUST_REBOOT );
}
else
{
if( EXIT_CODE_NOTHING_CHANGED == dwExitCode )
{
DisplayMessage( IDS_NOTHING_CHANGED );
}
}
exit( dwExitCode );
}
//////////////////////////////////////////////////
void
DumpStatusFromRegistry(
LPCTSTR strKernelModuleName )
{
HKEY MmKey = NULL;
DWORD dwExitCode;
LONG lOpenResult;
DWORD dwCrtFlags;
DWORD dwIoLevel;
int nKernelModuleNameLen;
int nStringLen;
BOOL bKernelVerified;
BOOL bIsModuleNameRegistry;
TCHAR *pstrCrtNameMatch, *pstrSubstring, *pCrtChar;
TCHAR strVrfDriver [VRF_MAX_DRIVER_STRING_LENGTH];
dwExitCode = EXIT_CODE_NOTHING_CHANGED;
bKernelVerified = FALSE;
//
// Open the Mm key
//
lOpenResult = RegOpenKeyEx (
HKEY_LOCAL_MACHINE,
RegMemoryManagementKeyName,
0,
KEY_QUERY_VALUE,
&MmKey);
if (lOpenResult != ERROR_SUCCESS)
{
dwExitCode = EXIT_CODE_ERROR;
if( lOpenResult == ERROR_ACCESS_DENIED )
{
DisplayMessage( IDS_ACCESS_IS_DENIED );
}
else
{
DisplayMessage(
IDS_REGOPENKEYEX_FAILED,
RegMemoryManagementKeyName,
(DWORD)lOpenResult);
}
}
else
{
if( ReadMmString (MmKey, RegVerifyDriversValueName, strVrfDriver) == FALSE)
{
dwExitCode = EXIT_CODE_ERROR;
}
else
{
bIsModuleNameRegistry = IsModuleNameAlreadyInRegistry(
strKernelModuleName,
strVrfDriver );
if( bIsModuleNameRegistry == TRUE )
{
//
// we have 'ntoskrnl.exe' in the registry
//
//
// read the verification flags
//
if( ReadRegistryValue( MmKey, RegVerifyDriverLevelValueName, &dwCrtFlags ) == FALSE )
{
dwExitCode = EXIT_CODE_ERROR;
}
else
{
bKernelVerified = TRUE;
if( dwCrtFlags != -1 )
{
if( ( dwCrtFlags & DRIVER_VERIFIER_IO_CHECKING ) != 0 )
{
//
// the IO verification is enabled, check the IO verification level ( 1 or 2 )
//
if( GetIoVerificationLevel( &dwIoLevel ) == FALSE )
{
dwExitCode = EXIT_CODE_ERROR;
}
else
{
if( dwIoLevel != 2 )
{
//
// only levels 1 & 2 are supported
//
dwIoLevel = 1;
}
DisplayMessage(
IDS_VERIFIER_ENABLED_WITH_IO_FORMAT,
strKernelModuleName,
dwCrtFlags,
dwIoLevel );
}
}
else
{
//
// the IO verification is not enabled
//
DisplayMessage(
IDS_VERIFIER_ENABLED_FORMAT,
strKernelModuleName,
dwCrtFlags );
}
}
else
{
DisplayMessage(
IDS_VERIFIER_ENABLED_NOFLAGS_FORMAT,
strKernelModuleName );
}
}
}
if( EXIT_CODE_NOTHING_CHANGED == dwExitCode && ! bKernelVerified )
{
DisplayMessage(
IDS_VERIFIER_NOT_ENABLED_FORMAT,
strKernelModuleName );
}
}
RegCloseKey (MmKey);
}
exit( dwExitCode );
}
//////////////////////////////////////////////////////////////////////
//////////////////////////////// Local registry manipulation functions
//////////////////////////////////////////////////////////////////////
BOOL
ReadRegistryValue (
HKEY HKey,
LPCTSTR Name,
DWORD * Value)
{
LONG Result;
DWORD Reserved;
DWORD Type;
DWORD Size;
//
// default value
//
*Value = -1;
Size = sizeof *Value;
Result = RegQueryValueEx (
HKey,
Name,
0,
&Type,
(LPBYTE)(Value),
&Size);
//
// Deal with a value that is not defined.
//
if (Result == ERROR_FILE_NOT_FOUND)
{
*Value = -1;
return TRUE;
}
if (Result != ERROR_SUCCESS)
{
DisplayMessage (
IDS_REGQUERYVALUEEX_FAILED,
Name,
(DWORD)Result);
return FALSE;
}
if (Type != REG_DWORD)
{
DisplayMessage (
IDS_REGQUERYVALUEEX_UNEXP_TYPE,
Name);
return FALSE;
}
if (Size != sizeof *Value)
{
DisplayMessage (
IDS_REGQUERYVALUEEX_UNEXP_SIZE,
Name);
return FALSE;
}
return TRUE;
}
BOOL
WriteRegistryValue (
HKEY HKey,
LPCTSTR Name,
DWORD Value)
{
LONG Result;
Result = RegSetValueEx (
HKey,
Name,
0,
REG_DWORD,
(LPBYTE)(&Value),
sizeof Value);
if (Result != ERROR_SUCCESS)
{
DisplayMessage (
IDS_REGSETVALUEEX_FAILED,
Name,
(DWORD)Result);
return FALSE;
}
return TRUE;
}
BOOL
ReadMmString (
HKEY MmKey,
LPCTSTR Name,
LPTSTR Value)
{
LONG Result;
DWORD Reserved;
DWORD Type;
DWORD Size;
//
// default value
//
*Value = 0;
Size = VRF_MAX_DRIVER_STRING_LENGTH;
Result = RegQueryValueEx (
MmKey,
Name,
0,
&Type,
(LPBYTE)(Value),
&Size);
//
// Deal with a value that is not defined.
//
if (Result == ERROR_FILE_NOT_FOUND)
{
*Value = 0;
return TRUE;
}
if (Result != ERROR_SUCCESS)
{
DisplayMessage (
IDS_REGQUERYVALUEEX_FAILED,
Name,
(DWORD)Result);
return FALSE;
}
if (Type != REG_SZ)
{
DisplayMessage (
IDS_REGQUERYVALUEEX_UNEXP_TYPE,
Name);
return FALSE;
}
return TRUE;
}
BOOL
WriteMmString (
HKEY MmKey,
LPCTSTR Name,
LPTSTR Value)
{
LONG Result;
DWORD Reserved;
DWORD Type;
DWORD Size;
Result = RegSetValueEx (
MmKey,
Name,
0,
REG_SZ,
(LPBYTE)(Value),
(_tcslen (Value) + 1) * sizeof (TCHAR));
if (Result != ERROR_SUCCESS)
{
DisplayMessage (
IDS_REGSETVALUEEX_FAILED,
Name,
(DWORD)Result);
return FALSE;
}
return TRUE;
}
//////////////////////////////////////////////////
BOOL
IsModuleNameAlreadyInRegistry(
LPCTSTR strKernelModuleName,
LPCTSTR strWholeString )
{
BOOL bAlreadyInRegistry;
int nKernelNameLength;
LPCTSTR strString;
LPCTSTR strSubstring;
TCHAR cBefore;
nKernelNameLength = _tcslen( strKernelModuleName );
//
// let's assume 'ntoskrnl.exe" is not already in the registry
//
bAlreadyInRegistry = FALSE;
//
// parse the string that's already in the registry
//
strString = strWholeString;
while( *strString != (TCHAR)0 )
{
strSubstring = _tcsstr( strString, strKernelModuleName );
if( strSubstring != NULL )
{
//
// the string from the registry includes "ntoskrnl.exe"
//
//
// let's assume it's nothing like "xyzntoskrnl.exe", "ntoskrnl.exexyz", etc.
//
bAlreadyInRegistry = TRUE;
//
// look for a character before the current substring
//
if( strSubstring > strWholeString )
{
//
// have at least one character before "ntoskrnl.exe" - look if it is blanc
//
cBefore = *( strSubstring - 1 );
if( cBefore != _T( ' ' ) && cBefore != _T( '\t' ) )
{
//
// the character before "ntoskrnl.exe" is non-blanc -> not the name we are searching for
//
bAlreadyInRegistry = FALSE;
}
}
//
// look for a character after the current substring
//
if( bAlreadyInRegistry == TRUE &&
strSubstring[ nKernelNameLength ] != (TCHAR)0 &&
strSubstring[ nKernelNameLength ] != _T( ' ' ) &&
strSubstring[ nKernelNameLength ] != _T( '\t' ) )
{
//
// have a non-blanc character after this substring -> not the name we are searching for
//
bAlreadyInRegistry = FALSE;
}
if( bAlreadyInRegistry == FALSE )
{
//
// this is not a real occurence of the name we are serching for, go further on
//
strString = strSubstring + 1;
}
if( bAlreadyInRegistry == TRUE )
{
//
// found it
//
break;
}
}
else
{
//
// the name is not there
//
break;
}
}
return bAlreadyInRegistry;
}
//////////////////////////////////////////////////
BOOL
GetIoVerificationLevel(
DWORD *pdwIoLevel )
{
LONG lResult;
HKEY IoKey = NULL;
DWORD dwCrtIoVerifLevel;
BOOL bFatalError;
bFatalError = FALSE;
//
// default value
//
*pdwIoLevel = 1;
//
// open the "I/O" key
//
lResult = RegOpenKeyEx (
HKEY_LOCAL_MACHINE,
RegIOVerifyKeyName,
0,
KEY_QUERY_VALUE,
&IoKey);
if (lResult != ERROR_SUCCESS)
{
//
// cannot open the IO key
//
if( lResult != ERROR_FILE_NOT_FOUND )
{
//
// the IO key is there, but we cannot open it
//
if( lResult == ERROR_ACCESS_DENIED )
{
DisplayMessage( IDS_ACCESS_IS_DENIED );
}
else
{
DisplayMessage(
IDS_REGOPENKEYEX_FAILED,
RegIOVerifyKeyName,
(DWORD)lResult);
}
bFatalError = TRUE;
}
// else - the IO key doesn't exist - use default value
}
else
{
//
// read "I/O System\IoVerifierLevel" value
//
if( ReadRegistryValue( IoKey, RegIOVerifyLevelValueName, &dwCrtIoVerifLevel ) )
{
if( LEVEL2_IO_VERIFIER_ENABLED_VALUE == dwCrtIoVerifLevel )
{
//
// we are at level 2 IO verification
//
*pdwIoLevel = 2;
}
}
RegCloseKey (IoKey);
}
return ( ! bFatalError );
}
//////////////////////////////////////////////////
BOOL
SwitchIoVerificationLevel(
DWORD dwNewIoLevel )
{
BOOL bFatalError;
LONG lResult;
HKEY IoKey = NULL;
HKEY SmKey = NULL;
bFatalError = FALSE;
//
// Open the "I/O System" key
//
lResult = RegOpenKeyEx (
HKEY_LOCAL_MACHINE,
RegIOVerifyKeyName,
0,
KEY_QUERY_VALUE | KEY_WRITE,
&IoKey);
if( lResult != ERROR_SUCCESS )
{
if( dwNewIoLevel == 2 )
{
//
// cannot open the IO key - maybe a fatal error - anyway, we will try to create it
//
bFatalError = TRUE;
if( lResult == ERROR_ACCESS_DENIED )
{
//
// access is denied - fatal error
//
DisplayMessage( IDS_ACCESS_IS_DENIED );
}
else
{
if( lResult == ERROR_FILE_NOT_FOUND )
{
//
// the "I/O System" key doesn't exist, try to create it
//
//
// open the "Session Manager" key
//
lResult = RegOpenKeyEx (
HKEY_LOCAL_MACHINE,
RegSessionManagerKeyName,
0,
KEY_QUERY_VALUE | KEY_WRITE,
&SmKey);
if( lResult != ERROR_SUCCESS )
{
//
// cannot open the "Session Manager" key - fatal error
//
DisplayMessage(
IDS_REGOPENKEYEX_FAILED,
RegSessionManagerKeyName,
(DWORD)lResult);
}
else
{
//
// create the "I/O System" key
//
lResult = RegCreateKeyEx(
SmKey,
RegIOVerifySubKeyName,
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_WRITE | KEY_QUERY_VALUE,
NULL,
&IoKey,
NULL );
if( lResult != ERROR_SUCCESS )
{
//
// cannot create key - fatal error
//
DisplayMessage(
IDS_REGCREATEKEYEX_FAILED,
RegIOVerifySubKeyName,
(DWORD)lResult);
}
else
{
//
// key created - reset the error code
//
bFatalError = FALSE;
}
//
// close the "Session Manager" key
//
lResult = RegCloseKey(
SmKey );
}
}
else
{
//
// other error opening the "I/O System" key
//
DisplayMessage(
IDS_REGOPENKEYEX_FAILED,
RegIOVerifyKeyName,
(DWORD)lResult);
}
}
}
else
bFatalError = TRUE;
//else
// we don't actually need the key in this case, we just want to wipe out
// the IO verification registry value
//
}
if( bFatalError == FALSE )
{
if( dwNewIoLevel == 2 )
{
//
// if we reached this point, we should have the IO key opened
//
//
// enable level 2
//
if( WriteRegistryValue( IoKey, RegIOVerifyLevelValueName, LEVEL2_IO_VERIFIER_ENABLED_VALUE ) == FALSE )
{
//
// cannot recover from this
//
bFatalError = TRUE;
}
}
else
{
//
// disable level 2
//
lResult = RegDeleteValue(
IoKey,
RegIOVerifyLevelValueName );
if( lResult != ERROR_SUCCESS && lResult != ERROR_FILE_NOT_FOUND )
{
bFatalError = TRUE;
DisplayMessage(
IDS_REGDELETEVALUE_FAILED,
RegIOVerifyLevelValueName,
(DWORD)lResult);
}
RegCloseKey (IoKey);
}
}
return ( ! bFatalError );
}