1788 lines
43 KiB
C
1788 lines
43 KiB
C
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
MsgMgr.c
|
|
|
|
Abstract:
|
|
|
|
Message Manager allows messages to be conditioned on some setup event.
|
|
Messages are of two kinds:
|
|
|
|
1) Those that depend on the migration status of ONE system object
|
|
-- a directory or registry key, for example.
|
|
|
|
2) Those that depend on GROUPS of objects.
|
|
|
|
We're using the phrase "Handleable Object" (HO) to mean something in the
|
|
Win95 system capable of having a migration status -- of being "handled."
|
|
HOs can be either files, directories, or registry keys with optional value
|
|
names. HOs are always stored as strings. In the case of registry keys,
|
|
strings are always "encoded" to guarantee they contain only lower-ANSI
|
|
printable chars.
|
|
|
|
A "Conditional Message Context" (or Context) is the association between
|
|
one or more HOs and a message, which will be printed if all HOs are not
|
|
eventually handled.
|
|
|
|
A message has two parts: a title (called "Component")
|
|
describe the group of HOs and an associated message, which is to be printed
|
|
if the HOs are not all marked "handled".
|
|
|
|
An Object Message Block (OMB) is a structure that
|
|
describes the pairing of a HO with either a Context or a message.
|
|
|
|
The string table 'g_HandledObjects' records which HOs are handled.
|
|
|
|
Here are the Message Manager's externally visible functions:
|
|
|
|
MsgMgr_Init()
|
|
|
|
Called once at the start of Win9x setup to initialize Message Manager.
|
|
|
|
MsgMgr_Cleanup()
|
|
|
|
Called once in Win9x setup, after software-incompatibility message have
|
|
been displayed. Frees the resources owned by Message Manager.
|
|
|
|
MsgMgr_ContextMsg_Add(
|
|
ContextName, // Context, e.g., "Plugin[Corel][Draw]"
|
|
ComponentName, // Message title, e.g., "Corel Draw"
|
|
Message); // Message text, e.g., "Corel Draw doesn't..."
|
|
|
|
Creates a context and message text.
|
|
|
|
MsgMgr_LinkObjectWithContext(
|
|
ContextName, // Context
|
|
ObjectName); // HO
|
|
|
|
Records the fact that the context message depends on the handled
|
|
state of the HO, ObjectName.
|
|
|
|
MsgMgr_ObjectMsg_Add(
|
|
ObjectName, // HO, e.g., C:\\corel\draw.exe
|
|
ComponentName, // Message title, e.g., "Corel Draw"
|
|
Message); // Message text, e.g., "Draw.exe doesn't ..."
|
|
|
|
Associates a message with a single HO.
|
|
|
|
IsReportObjectHandled (Object)
|
|
Checks to see if a specific object has been marked as handled.
|
|
|
|
IsReportObjectIncompatible (Object)
|
|
Checks to see if a specific object is in the list of incompatible objects.
|
|
|
|
Implementation:
|
|
|
|
Contexts are stored in StringTables. The Context name is the
|
|
key; pointers to the component name and message text are in extra data.
|
|
|
|
The association between HOs, on one hand, and contexts and messages on
|
|
the other, is stored in a table of Object Message Blocks, or OMBs.
|
|
|
|
During Win9x setup, OMBs are added, and objects are independently
|
|
marked as "handled". When all info has been collected, the list of
|
|
handled objects is compared with the list of OMBs. Object messages are
|
|
displayed if their object has not been handled; Context messages are
|
|
displayed if at least SOME of their objects have not been handled.
|
|
|
|
Author:
|
|
|
|
Mike Condra 20-May-1997
|
|
|
|
Revision History:
|
|
|
|
marcw 08-Mar-1999 Added support for handling Answer File items.
|
|
jimschm 15-Jan-1999 Moved code from migdll9x.c to here (more centralized)
|
|
jimschm 23-Dec-1998 Cleaned up
|
|
jimschm 23-Sep-1998 Revised to use new fileops
|
|
calinn 15-Jan-1997 Modified MsgMgr_ObjectMsg_Add to get a null message
|
|
mikeco 24-Sep-1997 Re-enabled context message code
|
|
marcw 21-Jul-1997 Added IsIncompatibleObject/IsReportObjectHandled functions.
|
|
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
#include "uip.h"
|
|
|
|
#define DBG_MSGMGR "MsgMgr"
|
|
|
|
#define S_MSG_STRING_MAPS TEXT("Report String Mappings")
|
|
|
|
|
|
typedef struct {
|
|
PCTSTR Component;
|
|
PCTSTR Message;
|
|
} CONTEXT_DATA, *P_CONTEXT_DATA;
|
|
|
|
//
|
|
// Object Message Block (OMBs). An OMB describes a Handleable Object's relation to a
|
|
// message. Either the OMB itself contains a message, or it points to a context with a
|
|
// message.
|
|
|
|
// For Handleable Object there is at most one OMB with a message. This amounts to saying
|
|
// that Handleable Objects have only one message. However, Handleable Objects may refer
|
|
// to (i.e., participate in) more than one context.
|
|
//
|
|
typedef struct {
|
|
// Flags that are set in the process of deciding when a context's message should
|
|
// be displayed.
|
|
BOOL Disabled;
|
|
PTSTR Object;
|
|
PTSTR Context;
|
|
PTSTR Component;
|
|
PTSTR Description;
|
|
} OBJ_MSG_BLOCK, *P_OBJ_MSG_BLOCK;
|
|
|
|
|
|
////////////////////// PUBLIC INTERFACE DESCRIPTION //////////////////////////
|
|
//
|
|
// Defined for callers in inc\msgmgr.c
|
|
//
|
|
|
|
//
|
|
// Function marks an object as "handled"
|
|
//
|
|
|
|
|
|
DWORD
|
|
pDfsGetFileAttributes (
|
|
IN PCTSTR Object
|
|
);
|
|
|
|
|
|
HASHTABLE g_ContextMsgs = NULL;
|
|
HASHTABLE g_LinkTargetDesc = NULL;
|
|
PVOID g_MsgMgrPool = NULL;
|
|
HASHTABLE g_HandledObjects = NULL;
|
|
HASHTABLE g_BlockingObjects = NULL;
|
|
HASHTABLE g_ElevatedObjects = NULL;
|
|
INT g_OmbEntriesMax = 0;
|
|
INT g_OmbEntries = 0;
|
|
P_OBJ_MSG_BLOCK *g_OmbList = NULL;
|
|
BOOL g_BlockingAppFound = FALSE;
|
|
PMAPSTRUCT g_MsgMgrMap = NULL;
|
|
|
|
|
|
BOOL
|
|
pAddBadSoftwareWrapper (
|
|
IN PCTSTR Object,
|
|
IN PCTSTR Component,
|
|
IN PCTSTR Message
|
|
)
|
|
{
|
|
DWORD offset;
|
|
BOOL includeInShortReport = FALSE;
|
|
|
|
if (HtFindString (g_BlockingObjects, Object)) {
|
|
g_BlockingAppFound = TRUE;
|
|
}
|
|
|
|
if (HtFindString (g_ElevatedObjects, Object)) {
|
|
includeInShortReport = TRUE;
|
|
}
|
|
|
|
//
|
|
// add this info to memdb first
|
|
//
|
|
MemDbSetValueEx (MEMDB_CATEGORY_COMPATREPORT, MEMDB_ITEM_COMPONENTS, Component, NULL, 0, &offset);
|
|
MemDbSetValueEx (MEMDB_CATEGORY_COMPATREPORT, MEMDB_ITEM_OBJECTS, Object, NULL, offset, NULL);
|
|
|
|
return AddBadSoftware (Component, Message, includeInShortReport);
|
|
}
|
|
|
|
|
|
typedef enum {
|
|
OT_FILE,
|
|
OT_DIRECTORY,
|
|
OT_REGISTRY,
|
|
OT_INIFILE,
|
|
OT_GUID,
|
|
OT_USERNAME,
|
|
OT_REPORT,
|
|
OT_ANSWERFILE,
|
|
OT_BADGUID
|
|
} OBJECT_TYPE;
|
|
|
|
VOID
|
|
pOmbAdd(
|
|
IN PCTSTR Object,
|
|
IN PCTSTR Context,
|
|
IN PCTSTR Component,
|
|
IN PCTSTR Description
|
|
);
|
|
|
|
VOID
|
|
pSuppressObjectReferences (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
pDisplayObjectMsgs (
|
|
VOID
|
|
);
|
|
|
|
BOOL
|
|
pFindLinkTargetDescription(
|
|
IN PCTSTR Target,
|
|
OUT PCTSTR* StrDesc
|
|
);
|
|
|
|
BOOL
|
|
IsWacked(
|
|
IN PCTSTR str
|
|
);
|
|
|
|
|
|
|
|
BOOL
|
|
pTranslateThisRoot (
|
|
PCSTR UnFixedRegKey,
|
|
PCSTR RootWithWack,
|
|
PCSTR NewRoot,
|
|
PSTR *FixedRegKey
|
|
)
|
|
{
|
|
UINT RootByteLen;
|
|
|
|
RootByteLen = ByteCountA (RootWithWack);
|
|
|
|
if (StringIMatchByteCountA (RootWithWack, UnFixedRegKey, RootByteLen)) {
|
|
|
|
*FixedRegKey = DuplicateTextA (UnFixedRegKey);
|
|
StringCopyA (*FixedRegKey, NewRoot);
|
|
StringCopyA (AppendWackA (*FixedRegKey), (PCSTR) ((PBYTE) UnFixedRegKey + RootByteLen));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
PSTR
|
|
pTranslateRoots (
|
|
PCSTR UnFixedRegKey
|
|
)
|
|
{
|
|
PSTR FixedRegKey;
|
|
|
|
if (pTranslateThisRoot (UnFixedRegKey, "HKEY_LOCAL_MACHINE\\", "HKLM", &FixedRegKey) ||
|
|
pTranslateThisRoot (UnFixedRegKey, "HKEY_CLASSES_ROOT\\", "HKLM\\Software\\Classes", &FixedRegKey) ||
|
|
pTranslateThisRoot (UnFixedRegKey, "HKCR\\", "HKLM\\Software\\Classes", &FixedRegKey) ||
|
|
pTranslateThisRoot (UnFixedRegKey, "HKEY_ROOT\\", "HKR", &FixedRegKey) ||
|
|
pTranslateThisRoot (UnFixedRegKey, "HKEY_CURRENT_USER\\", "HKR", &FixedRegKey) ||
|
|
pTranslateThisRoot (UnFixedRegKey, "HKCU\\", "HKR", &FixedRegKey) ||
|
|
pTranslateThisRoot (UnFixedRegKey, "HKEY_CURRENT_CONFIG\\", "HKLM\\System\\CurrentControlSet", &FixedRegKey) ||
|
|
pTranslateThisRoot (UnFixedRegKey, "HKCC\\", "HKLM\\System\\CurrentControlSet", &FixedRegKey)
|
|
) {
|
|
FreeText (UnFixedRegKey);
|
|
return FixedRegKey;
|
|
}
|
|
|
|
return (PSTR) UnFixedRegKey;
|
|
}
|
|
|
|
VOID
|
|
ElevateObject (
|
|
IN PCTSTR Object
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
ElevateObject puts a file in the elevated object table, so that
|
|
it will always appear on the short version of the report summary.
|
|
|
|
Arguments:
|
|
|
|
Object - Specifies a caller-encoded object string
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
HtAddString (g_ElevatedObjects, Object);
|
|
}
|
|
|
|
|
|
VOID
|
|
HandleReportObject (
|
|
IN PCTSTR Object
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HandleReportObject adds a caller-encoded object string to the handled hash
|
|
table. This causes any message for the object to be suppressed.
|
|
|
|
Arguments:
|
|
|
|
Object - Specifies a caller-encoded object string
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
HtAddString (g_HandledObjects, Object);
|
|
}
|
|
|
|
|
|
VOID
|
|
AddBlockingObject (
|
|
IN PCTSTR Object
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
AddBlockingObject adds a file to be a blocking file. If this file is not handled there
|
|
will be a warning box after user report page.
|
|
|
|
Arguments:
|
|
|
|
Object - Specifies a caller-encoded object string
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
HtAddString (g_BlockingObjects, Object);
|
|
}
|
|
|
|
|
|
VOID
|
|
HandleObject(
|
|
IN PCTSTR Object,
|
|
IN PCTSTR ObjectType
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HandleObject adds a caller-encoded object string to the handled hash table,
|
|
and also marks a file as handled in fileops, if Object is a file.
|
|
|
|
Arguments:
|
|
|
|
Object - Specifies a caller-encoded object string
|
|
|
|
ObjectType - Specifies the object type (File, Directory, Registry, Report)
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD Attribs;
|
|
OBJECT_TYPE Type;
|
|
PTSTR p;
|
|
TCHAR LongPath[MAX_TCHAR_PATH];
|
|
BOOL SuppressRegistry = TRUE;
|
|
CHAR IniPath[MAX_MBCHAR_PATH * 2];
|
|
BOOL IniSaved;
|
|
PCSTR ValueName, SectionName;
|
|
TCHAR Node[MEMDB_MAX];
|
|
PTSTR FixedObject;
|
|
TREE_ENUM Files;
|
|
DWORD attribs;
|
|
|
|
if (StringIMatch (ObjectType, TEXT("File"))) {
|
|
|
|
Type = OT_FILE;
|
|
|
|
} else if (StringIMatch (ObjectType, TEXT("Directory"))) {
|
|
|
|
Type = OT_DIRECTORY;
|
|
|
|
} else if (StringIMatch (ObjectType, TEXT("Registry"))) {
|
|
|
|
Type = OT_REGISTRY;
|
|
|
|
} else if (StringIMatch (ObjectType, TEXT("IniFile"))) {
|
|
|
|
Type = OT_INIFILE;
|
|
|
|
} else if (StringIMatch (ObjectType, TEXT("GUID"))) {
|
|
|
|
Type = OT_GUID;
|
|
|
|
} else if (StringIMatch (ObjectType, TEXT("BADGUID"))) {
|
|
|
|
Type = OT_BADGUID;
|
|
|
|
} else if (StringIMatch (ObjectType, TEXT("UserName"))) {
|
|
|
|
Type = OT_USERNAME;
|
|
|
|
} else if (StringIMatch (ObjectType, TEXT("Report"))) {
|
|
|
|
Type = OT_REGISTRY;
|
|
SuppressRegistry = FALSE;
|
|
|
|
} else if (StringIMatch (ObjectType, TEXT("AnswerFile"))) {
|
|
|
|
Type = OT_ANSWERFILE;
|
|
|
|
} else {
|
|
|
|
DEBUGMSG ((DBG_ERROR, "Object %s ignored; invalid object type: %s", Object, ObjectType));
|
|
return;
|
|
|
|
}
|
|
|
|
if (Type == OT_FILE || Type == OT_DIRECTORY) {
|
|
|
|
if (!OurGetLongPathName (
|
|
Object,
|
|
LongPath,
|
|
sizeof (LongPath) / sizeof (LongPath[0])
|
|
)) {
|
|
|
|
DEBUGMSG ((DBG_ERROR, "Object %s ignored; invalid path", Object));
|
|
return;
|
|
|
|
}
|
|
|
|
Attribs = pDfsGetFileAttributes (LongPath);
|
|
|
|
if (Attribs != INVALID_ATTRIBUTES && !(Attribs & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
|
|
//
|
|
// It's got to be a file, not a directory!
|
|
//
|
|
|
|
DontTouchThisFile (LongPath);
|
|
MarkPathAsHandled (LongPath);
|
|
MarkFileForBackup (LongPath);
|
|
DEBUGMSG ((DBG_MSGMGR, "Backing up %s", LongPath));
|
|
|
|
} else if (Attribs != INVALID_ATTRIBUTES) {
|
|
|
|
//
|
|
// LongPath is a directory. If its the root, %windir%, %windir%\system or
|
|
// Program Files, then ignore it.
|
|
//
|
|
|
|
p = _tcschr (LongPath, TEXT('\\'));
|
|
if (p) {
|
|
p = _tcschr (p + 1, TEXT('\\'));
|
|
}
|
|
|
|
if (!p) {
|
|
DEBUGMSG ((DBG_ERROR, "Object %s ignored, can't handle root dirs", Object));
|
|
return;
|
|
}
|
|
|
|
if (!StringIMatchA (LongPath, g_WinDir) &&
|
|
!StringIMatchA (LongPath, g_SystemDir) &&
|
|
!StringIMatchA (LongPath, g_ProgramFilesDir)
|
|
) {
|
|
|
|
if (IsDriveExcluded (LongPath)) {
|
|
DEBUGMSG ((DBG_WARNING, "Skipping handled dir %s because it is excluded", LongPath));
|
|
} else if (!IsDriveAccessible (LongPath)) {
|
|
DEBUGMSG ((DBG_WARNING, "Skipping handled dir %s because it is not accessible", LongPath));
|
|
} else {
|
|
|
|
//
|
|
// Let's enumerate this tree and do the right thing
|
|
//
|
|
if (EnumFirstFileInTree (&Files, LongPath, NULL, TRUE)) {
|
|
do {
|
|
DontTouchThisFile (Files.FullPath);
|
|
MarkPathAsHandled (Files.FullPath);
|
|
|
|
//
|
|
// back up file, or make sure empty dir is restored
|
|
//
|
|
|
|
if (g_ConfigOptions.EnableBackup != TRISTATE_NO) {
|
|
|
|
if (!Files.Directory) {
|
|
DEBUGMSG ((DBG_MSGMGR, "Backing up %s", Files.FullPath));
|
|
MarkFileForBackup (Files.FullPath);
|
|
} else {
|
|
DEBUGMSG ((DBG_MSGMGR, "Preserving possible empty dir %s", Files.FullPath));
|
|
|
|
attribs = Files.FindData->dwFileAttributes;
|
|
if (attribs == FILE_ATTRIBUTE_DIRECTORY) {
|
|
attribs = 0;
|
|
}
|
|
|
|
MemDbSetValueEx (
|
|
MEMDB_CATEGORY_EMPTY_DIRS,
|
|
Files.FullPath,
|
|
NULL,
|
|
NULL,
|
|
attribs,
|
|
NULL
|
|
);
|
|
}
|
|
}
|
|
|
|
} while (EnumNextFileInTree (&Files));
|
|
}
|
|
}
|
|
|
|
DontTouchThisFile (LongPath);
|
|
MarkPathAsHandled (LongPath);
|
|
|
|
} else {
|
|
|
|
DEBUGMSG ((DBG_ERROR, "Object %s ignored, can't handle big dirs", Object));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Put an ending wack on the object so it handles all subitems in the report
|
|
//
|
|
|
|
LongPath[MAX_TCHAR_PATH - 2] = 0;
|
|
AppendPathWack (LongPath);
|
|
|
|
} else {
|
|
|
|
DEBUGMSG ((
|
|
DBG_WARNING,
|
|
"Object %s ignored; it does not exist or is not a complete local path (%s)",
|
|
Object,
|
|
LongPath
|
|
));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//
|
|
// Make sure messages for the file or dir are removed
|
|
//
|
|
|
|
HandleReportObject (LongPath);
|
|
|
|
} else if (Type == OT_REGISTRY) {
|
|
|
|
if (_tcsnextc (Object) == '*') {
|
|
|
|
HandleReportObject (Object);
|
|
|
|
} else {
|
|
|
|
if (!_tcschr (Object, '[')) {
|
|
//
|
|
// This reg object does not have a value
|
|
//
|
|
|
|
FixedObject = AllocText (SizeOfStringA (Object) + sizeof (CHAR)*2);
|
|
MYASSERT (FixedObject);
|
|
StringCopy (FixedObject, Object);
|
|
AppendWack (FixedObject);
|
|
|
|
FixedObject = pTranslateRoots (FixedObject);
|
|
MYASSERT (FixedObject);
|
|
|
|
//
|
|
// Handle messages for the registry key and all of its subkeys
|
|
//
|
|
|
|
HandleReportObject (FixedObject);
|
|
|
|
//
|
|
// Put a star on it so the entire node is suppressed
|
|
//
|
|
|
|
StringCat (FixedObject, "*");
|
|
|
|
} else {
|
|
|
|
//
|
|
// This reg object has a value
|
|
//
|
|
|
|
FixedObject = DuplicateText (Object);
|
|
MYASSERT (FixedObject);
|
|
|
|
FixedObject = pTranslateRoots (FixedObject);
|
|
MYASSERT (FixedObject);
|
|
|
|
HandleReportObject (FixedObject);
|
|
}
|
|
|
|
//
|
|
// Make sure registry key is not suppressed
|
|
//
|
|
|
|
if (SuppressRegistry) {
|
|
Suppress95Object (FixedObject);
|
|
}
|
|
|
|
FreeText (FixedObject);
|
|
}
|
|
|
|
} else if (Type == OT_GUID) {
|
|
|
|
if (!IsGuid (Object, TRUE)) {
|
|
|
|
DEBUGMSG ((DBG_ERROR, "Object %s ignored because it's not a GUID", Object));
|
|
return;
|
|
}
|
|
|
|
HandleReportObject (Object);
|
|
|
|
} else if (Type == OT_BADGUID) {
|
|
|
|
if (!IsGuid (Object, TRUE)) {
|
|
|
|
DEBUGMSG ((DBG_ERROR, "Object %s ignored because it's not a GUID", Object));
|
|
return;
|
|
}
|
|
|
|
MemDbBuildKey (
|
|
Node,
|
|
MEMDB_CATEGORY_GUIDS,
|
|
NULL,
|
|
NULL,
|
|
Object
|
|
);
|
|
|
|
MemDbSetValue (Node, 0);
|
|
|
|
} else if (Type == OT_USERNAME) {
|
|
|
|
Node[0] = TEXT('|');
|
|
_tcssafecpy (Node + 1, Object, MAX_PATH);
|
|
|
|
HandleReportObject (Node);
|
|
|
|
} else if (Type == OT_INIFILE) {
|
|
|
|
IniSaved = FALSE;
|
|
ValueName = NULL;
|
|
SectionName = NULL;
|
|
|
|
//
|
|
// Verify the INI file exists
|
|
//
|
|
|
|
StringCopyByteCount (IniPath, Object, sizeof (IniPath));
|
|
|
|
//
|
|
// Inf INI file is a path without section, then give an error
|
|
//
|
|
|
|
if (OurGetLongPathName (
|
|
IniPath,
|
|
LongPath,
|
|
sizeof (LongPath) / sizeof (LongPath[0])
|
|
)) {
|
|
|
|
DEBUGMSG ((DBG_ERROR, "INI file object %s ignored, must have section", Object));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Get the ValueName or SectionName
|
|
//
|
|
|
|
p = _tcsrchr (IniPath, TEXT('\\'));
|
|
if (p) {
|
|
*p = 0;
|
|
ValueName = p + 1;
|
|
|
|
if (!OurGetLongPathName (
|
|
IniPath,
|
|
LongPath,
|
|
sizeof (LongPath) / sizeof (LongPath[0])
|
|
)) {
|
|
|
|
//
|
|
// IniPath does not exist, must have both ValueName and SectionName
|
|
//
|
|
|
|
p = _tcsrchr (IniPath, TEXT('\\'));
|
|
|
|
if (p) {
|
|
//
|
|
// We now have both ValueName and SectionName, IniPath must point
|
|
// to a valid file
|
|
//
|
|
|
|
*p = 0;
|
|
SectionName = p + 1;
|
|
|
|
if (!OurGetLongPathName (
|
|
IniPath,
|
|
LongPath,
|
|
sizeof (LongPath) / sizeof (LongPath[0])
|
|
)) {
|
|
|
|
DEBUGMSG ((DBG_ERROR, "INI file object %s ignored, INI file not found", Object));
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
DEBUGMSG ((DBG_ERROR, "INI file object %s ignored, bad INI file", Object));
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// IniPath does exist, we know we only have a SectionName
|
|
//
|
|
|
|
SectionName = ValueName;
|
|
ValueName = TEXT("*");
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// No wacks in Object!!
|
|
//
|
|
|
|
DEBUGMSG ((DBG_ERROR, "INI file object %s ignored, bad object", Object));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Suppress the INI file settings from NT, and make sure report entries
|
|
// that come from INI files are also suppressed
|
|
//
|
|
|
|
MemDbBuildKey (
|
|
Node,
|
|
MEMDB_CATEGORY_SUPPRESS_INI_MAPPINGS,
|
|
IniPath,
|
|
SectionName,
|
|
ValueName
|
|
);
|
|
|
|
|
|
MemDbSetValue (Node, 0);
|
|
HandleReportObject (Node);
|
|
|
|
} else if (Type == OT_ANSWERFILE) {
|
|
|
|
StringCopy (Node, Object);
|
|
p = _tcschr (Node, TEXT('\\'));
|
|
|
|
if (p) {
|
|
|
|
*p = 0;
|
|
ValueName = _tcsinc (p);
|
|
}
|
|
else {
|
|
|
|
ValueName = TEXT("*");
|
|
}
|
|
|
|
SectionName = Node;
|
|
|
|
MemDbSetValueEx (
|
|
MEMDB_CATEGORY_SUPPRESS_ANSWER_FILE_SETTINGS,
|
|
SectionName,
|
|
ValueName,
|
|
NULL,
|
|
0,
|
|
NULL
|
|
);
|
|
}
|
|
ELSE_DEBUGMSG ((DBG_WHOOPS, "Object type %u for %s not recognized.", Type, Object));
|
|
}
|
|
|
|
|
|
VOID
|
|
MsgMgr_Init (
|
|
VOID
|
|
)
|
|
{
|
|
// Build message pool
|
|
g_MsgMgrPool = PoolMemInitNamedPool ("Message Manager");
|
|
|
|
// Table of handled objects
|
|
g_HandledObjects = HtAlloc();
|
|
|
|
// Table of blocking objects
|
|
g_BlockingObjects = HtAlloc();
|
|
|
|
// Table of objects to put on short summary
|
|
g_ElevatedObjects = HtAlloc();
|
|
|
|
// Context messages init
|
|
g_ContextMsgs = HtAllocWithData (sizeof(PCTSTR));
|
|
|
|
// Link-target description init
|
|
g_LinkTargetDesc = HtAllocWithData (sizeof(PVOID));
|
|
|
|
// Bad Software Init
|
|
g_OmbEntriesMax = 25;
|
|
g_OmbEntries = 0;
|
|
g_OmbList = MemAlloc(
|
|
g_hHeap,
|
|
0,
|
|
g_OmbEntriesMax * sizeof(P_OBJ_MSG_BLOCK)
|
|
);
|
|
}
|
|
|
|
VOID
|
|
pAddStaticHandledObjects (
|
|
VOID
|
|
)
|
|
{
|
|
INFSTRUCT is = INITINFSTRUCT_POOLHANDLE;
|
|
PCTSTR object;
|
|
|
|
if (InfFindFirstLine (g_Win95UpgInf, TEXT("IgnoreInReport"), NULL, &is)) {
|
|
do {
|
|
|
|
object = InfGetStringField (&is, 0);
|
|
|
|
if (object) {
|
|
HandleObject (object, TEXT("Report"));
|
|
}
|
|
|
|
} while (InfFindNextLine (&is));
|
|
|
|
InfCleanUpInfStruct (&is);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
MsgMgr_Resolve (
|
|
VOID
|
|
)
|
|
{
|
|
pAddStaticHandledObjects ();
|
|
pSuppressObjectReferences(); // disable references to handled objects
|
|
pDisplayObjectMsgs(); // print object & context msgs & enabled object refs
|
|
}
|
|
|
|
|
|
VOID
|
|
MsgMgr_Cleanup (
|
|
VOID
|
|
)
|
|
{
|
|
// Context message cleanup
|
|
HtFree (g_ContextMsgs);
|
|
g_ContextMsgs = NULL;
|
|
|
|
PoolMemDestroyPool(g_MsgMgrPool);
|
|
g_MsgMgrPool = NULL;
|
|
|
|
// Table of blocking objects
|
|
HtFree (g_BlockingObjects);
|
|
g_BlockingObjects = NULL;
|
|
|
|
// Table of elevated objects
|
|
HtFree (g_ElevatedObjects);
|
|
g_ElevatedObjects = NULL;
|
|
|
|
// Table of handled objects
|
|
HtFree (g_HandledObjects);
|
|
g_HandledObjects = NULL;
|
|
|
|
// Link description cleanup
|
|
HtFree (g_LinkTargetDesc);
|
|
g_LinkTargetDesc = NULL;
|
|
|
|
// Object-message list. Note, entries on list are entirely
|
|
// from g_MsgMgrPool.
|
|
if (NULL != g_OmbList) {
|
|
MemFree(g_hHeap, 0, g_OmbList);
|
|
g_OmbList = NULL;
|
|
}
|
|
|
|
if (g_MsgMgrMap) {
|
|
|
|
DestroyStringMapping (g_MsgMgrMap);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
MsgMgr_ObjectMsg_Add(
|
|
IN PCTSTR Object,
|
|
IN PCTSTR Component,
|
|
IN PCTSTR Message
|
|
)
|
|
{
|
|
|
|
MYASSERT(Object);
|
|
MYASSERT(Component);
|
|
|
|
pOmbAdd(
|
|
Object,
|
|
TEXT(""), // context
|
|
Component,
|
|
Message
|
|
);
|
|
}
|
|
|
|
|
|
PCTSTR
|
|
pGetMassagedComponent (
|
|
IN PCTSTR Component
|
|
)
|
|
{
|
|
|
|
TCHAR tempBuffer[MAX_TCHAR_PATH];
|
|
PCTSTR rString = NULL;
|
|
|
|
if (!Component) {
|
|
return NULL;
|
|
}
|
|
|
|
// Do string search and replacement and make own copy of the component.
|
|
if (MappingSearchAndReplaceEx (
|
|
g_MsgMgrMap,
|
|
Component,
|
|
tempBuffer,
|
|
0,
|
|
NULL,
|
|
sizeof (tempBuffer),
|
|
STRMAP_ANY_MATCH,
|
|
NULL,
|
|
NULL
|
|
)) {
|
|
DEBUGMSG ((DBG_MSGMGR, "Mapped %s to %s.", Component, tempBuffer));
|
|
rString = PoolMemDuplicateString(g_MsgMgrPool, tempBuffer);
|
|
}
|
|
else {
|
|
rString = PoolMemDuplicateString(g_MsgMgrPool, Component);
|
|
}
|
|
|
|
|
|
|
|
|
|
return rString;
|
|
|
|
}
|
|
|
|
VOID
|
|
MsgMgr_ContextMsg_Add(
|
|
IN PCTSTR Context,
|
|
IN PCTSTR Component,
|
|
IN PCTSTR Message
|
|
)
|
|
{
|
|
P_CONTEXT_DATA ContextData;
|
|
|
|
|
|
MYASSERT(Context);
|
|
MYASSERT(Component);
|
|
|
|
// Get a structure to hold the componentand message string pointers
|
|
ContextData = PoolMemGetMemory(g_MsgMgrPool, sizeof(CONTEXT_DATA));
|
|
|
|
|
|
// Do string search and replacement and make own copy of the component.
|
|
ContextData->Component = pGetMassagedComponent (Component);
|
|
|
|
// Make own copy of message
|
|
if (Message != NULL) {
|
|
ContextData->Message = PoolMemDuplicateString(g_MsgMgrPool, Message);
|
|
}
|
|
else {
|
|
ContextData->Message = NULL;
|
|
}
|
|
|
|
//
|
|
// Debug message
|
|
//
|
|
DEBUGMSG ((
|
|
DBG_MSGMGR,
|
|
"MsgMgr_ContextMsg_Add\n"
|
|
" obj: '%s'\n"
|
|
" ctx: '%s'\n"
|
|
" cmp: '%s'\n"
|
|
" msg: '%s'\n",
|
|
TEXT(""),
|
|
Context,
|
|
Component,
|
|
Message ? Message : TEXT("<No message>")
|
|
));
|
|
|
|
//
|
|
// Save component named and message in string table
|
|
//
|
|
|
|
HtAddStringAndData (
|
|
g_ContextMsgs,
|
|
Context,
|
|
&ContextData
|
|
);
|
|
|
|
}
|
|
|
|
|
|
BOOL
|
|
IsReportObjectHandled (
|
|
IN PCTSTR Object
|
|
)
|
|
{
|
|
HASHTABLE_ENUM e;
|
|
PCTSTR p, q, r;
|
|
PCTSTR End;
|
|
PTSTR LowerCaseObject;
|
|
BOOL b = FALSE;
|
|
|
|
//
|
|
// Check g_HandledObjects for:
|
|
//
|
|
// 1. An exact match
|
|
// 2. The handled object is the root of Object
|
|
//
|
|
|
|
if (HtFindString (g_HandledObjects, Object)) {
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// We know the hash table stores its strings in lower case
|
|
//
|
|
|
|
LowerCaseObject = JoinPaths (Object, TEXT(""));
|
|
_tcslwr (LowerCaseObject);
|
|
|
|
__try {
|
|
|
|
if (HtFindString (g_HandledObjects, LowerCaseObject)) {
|
|
b = TRUE;
|
|
__leave;
|
|
}
|
|
|
|
End = GetEndOfString (LowerCaseObject);
|
|
|
|
if (EnumFirstHashTableString (&e, g_HandledObjects)) {
|
|
do {
|
|
|
|
p = LowerCaseObject;
|
|
q = e.String;
|
|
|
|
// Guard against empty hash table strings
|
|
if (*q == 0) {
|
|
continue;
|
|
}
|
|
|
|
r = NULL;
|
|
|
|
//
|
|
// Check for substring match
|
|
//
|
|
|
|
while (*q && p < End) {
|
|
|
|
r = q;
|
|
if (_tcsnextc (p) != _tcsnextc (q)) {
|
|
break;
|
|
}
|
|
|
|
p = _tcsinc (p);
|
|
q = _tcsinc (q);
|
|
}
|
|
|
|
//
|
|
// We know the hash string cannot match identically, since
|
|
// we checked for an exact match earlier. To have a match,
|
|
// the hash string must be shorter than the object string,
|
|
// it must end in a wack, and *q must point to the nul.
|
|
//
|
|
|
|
MYASSERT (r);
|
|
|
|
if (*q == 0 && _tcsnextc (r) == TEXT('\\')) {
|
|
MYASSERT (p < End);
|
|
b = TRUE;
|
|
__leave;
|
|
}
|
|
|
|
} while (EnumNextHashTableString (&e));
|
|
}
|
|
}
|
|
__finally {
|
|
FreePathString (LowerCaseObject);
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
BOOL
|
|
IsReportObjectIncompatible (
|
|
IN PCTSTR Object
|
|
)
|
|
{
|
|
|
|
BOOL rIsIncompatible = FALSE;
|
|
DWORD i;
|
|
|
|
//
|
|
// First, the "handled" test... Check to see if the object is in the
|
|
// handled object table. If it is, then we can return FALSE.
|
|
//
|
|
if (!IsReportObjectHandled(Object)) {
|
|
|
|
//
|
|
// It wasn't in the table. Now we have to look the hard way!
|
|
// Traverse the list of incompatible objects and look for one
|
|
// that matches.
|
|
//
|
|
for (i=0; i < (DWORD) g_OmbEntries; i++) {
|
|
|
|
//
|
|
// If the current object in the incompatible list ends in a wack, do a
|
|
// prefix match. if the current incompatible object is a prefix of Object,
|
|
// then Object is incompatible.
|
|
//
|
|
if (IsWacked((g_OmbList[i])->Object)) {
|
|
if (StringIMatchCharCount((g_OmbList[i])->Object,Object,CharCount((g_OmbList[i])->Object))) {
|
|
rIsIncompatible = TRUE;
|
|
}
|
|
}
|
|
else {
|
|
//
|
|
// The current object does not end in a wack. Therefore, it is necessary
|
|
// to have a complete match.
|
|
//
|
|
if (StringIMatch((g_OmbList[i])->Object,Object)) {
|
|
rIsIncompatible = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return rIsIncompatible;
|
|
}
|
|
|
|
BOOL
|
|
pContextMsg_Find(
|
|
IN PCTSTR Context,
|
|
OUT PCTSTR* Component,
|
|
OUT PCTSTR* Message
|
|
)
|
|
{
|
|
P_CONTEXT_DATA ContextData;
|
|
|
|
if (HtFindStringAndData (g_ContextMsgs, Context, &ContextData)) {
|
|
*Component = ContextData->Component;
|
|
*Message = ContextData->Message;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
IsWacked(
|
|
IN PCTSTR str
|
|
)
|
|
{
|
|
PCTSTR pWack = _tcsrchr(str,_T('\\'));
|
|
return (NULL != pWack && 0 == *(pWack+1));
|
|
}
|
|
|
|
|
|
//
|
|
// This function is called for each Handled object in HandledObject.
|
|
// Objects are final or non-final, which can be known by looking for a final
|
|
// wack. It is caller's responsibility to ensure that directories and registry
|
|
// entries without value-names are wacked. This allows us to blow away (marked
|
|
// as handled) any other object with the wacked HO as a prefix.
|
|
//
|
|
|
|
BOOL
|
|
pDisplayContextMsgs_Callback(
|
|
IN HASHTABLE stringTable,
|
|
IN HASHITEM stringId,
|
|
IN PCTSTR Context,
|
|
IN PVOID extraData,
|
|
IN UINT extraDataSize,
|
|
IN LPARAM lParam
|
|
)
|
|
{
|
|
INT i;
|
|
P_OBJ_MSG_BLOCK Omb;
|
|
|
|
P_CONTEXT_DATA Data = *(P_CONTEXT_DATA*)extraData;
|
|
|
|
//
|
|
// Debug message
|
|
//
|
|
DEBUGMSG ((
|
|
DBG_MSGMGR,
|
|
"pDisplayContextMsgs_Callback\n"
|
|
" ctx: '%s'\n"
|
|
" cmp: '%s'\n"
|
|
" msg: '%s'\n",
|
|
Context,
|
|
Data->Component,
|
|
Data->Message
|
|
));
|
|
|
|
//
|
|
// Loop through the OMBs, looking for an enabled reference with our context.
|
|
// If found, print our message.
|
|
//
|
|
for (i = 0; i < g_OmbEntries; i++) {
|
|
|
|
Omb = *(g_OmbList + i);
|
|
|
|
//
|
|
// If enabled and matches our context, print us
|
|
//
|
|
if (!Omb->Disabled && StringIMatch (Context, Omb->Context)) {
|
|
|
|
//
|
|
// Debug message
|
|
//
|
|
DEBUGMSG((
|
|
DBG_MSGMGR,
|
|
"pDisplayContextMsgs_Callback: DISPLAYING\n"
|
|
" dsa: %d\n"
|
|
" ctx: '%s'\n"
|
|
" cmp: '%s'\n"
|
|
" msg: '%s'\n",
|
|
Omb->Disabled,
|
|
Omb->Context,
|
|
Data->Component,
|
|
Data->Message
|
|
));
|
|
|
|
pAddBadSoftwareWrapper (
|
|
Omb->Object,
|
|
Data->Component,
|
|
Data->Message
|
|
);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
UNREFERENCED_PARAMETER(stringTable);
|
|
UNREFERENCED_PARAMETER(stringId);
|
|
UNREFERENCED_PARAMETER(extraData);
|
|
UNREFERENCED_PARAMETER(lParam);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// This function is called for each Handled object in HandledObject.
|
|
// Objects are final or non-final, which can be known by looking for a final
|
|
// wack. It is caller's responsibility to ensure that directories and registry
|
|
// entries without value-names are wacked. This allows us to blow away (marked
|
|
// as handled) any other object with the wacked HO as a prefix.
|
|
//
|
|
BOOL
|
|
pSuppressObjectReferences_Callback(
|
|
IN HASHITEM stringTable,
|
|
IN HASHTABLE stringId,
|
|
IN PCTSTR HandledObject,
|
|
IN PVOID extraData,
|
|
IN UINT extraDataSize,
|
|
IN LPARAM lParam
|
|
)
|
|
{
|
|
INT nHandledLen;
|
|
BOOL IsNonFinalNode;
|
|
INT i;
|
|
P_OBJ_MSG_BLOCK Omb;
|
|
|
|
UNREFERENCED_PARAMETER(stringTable);
|
|
UNREFERENCED_PARAMETER(stringId);
|
|
UNREFERENCED_PARAMETER(extraData);
|
|
UNREFERENCED_PARAMETER(lParam);
|
|
|
|
//
|
|
// Find whether the HO is capable of having children. This is known by looking
|
|
// for a final wack.
|
|
//
|
|
IsNonFinalNode = IsWacked(HandledObject);
|
|
|
|
//
|
|
// Find how long it is (outside the following loop)
|
|
//
|
|
nHandledLen = ByteCount(HandledObject) - 1;
|
|
|
|
//
|
|
// Loop thru the list of messages. Apply one of two tests, depending on
|
|
// on whether the handled object is non-final.
|
|
//
|
|
for (i = 0; i < g_OmbEntries; i++) {
|
|
Omb = *(g_OmbList + i);
|
|
|
|
// If disabled skip
|
|
if (!Omb->Disabled) {
|
|
|
|
if (IsNonFinalNode) {
|
|
if (StringIMatchCharCount(
|
|
Omb->Object, // key to deferred message
|
|
HandledObject, // a handled object
|
|
nHandledLen
|
|
) && (Omb->Object[nHandledLen] == 0 || Omb->Object[nHandledLen] == '\\')) {
|
|
|
|
DEBUGMSG((
|
|
DBG_MSGMGR,
|
|
"pSuppressObjectReferences_Callback: SUPPRESSING NON-FINAL\n"
|
|
" obj: '%s'\n"
|
|
" why: '%s'\n"
|
|
" ctx: '%s'\n"
|
|
" cmp: '%s'\n"
|
|
" msg: '%s'\n",
|
|
Omb->Object,
|
|
HandledObject,
|
|
Omb->Context,
|
|
Omb->Component,
|
|
Omb->Description
|
|
));
|
|
|
|
Omb->Disabled = TRUE;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// When the handled object is a file (not a dir), then an exact match
|
|
// must exist with Key for the message to be suppressed.
|
|
//
|
|
if (StringIMatch (Omb->Object, HandledObject)) {
|
|
|
|
DEBUGMSG((
|
|
DBG_MSGMGR, "pSuppressObjectReferences_Callback: SUPPRESSING FINAL\n"
|
|
" obj: '%s'\n"
|
|
" why: '%s'\n"
|
|
" ctx: '%s'\n"
|
|
" cmp: '%s'\n"
|
|
" msg: '%s'\n",
|
|
Omb->Object,
|
|
HandledObject,
|
|
Omb->Context,
|
|
Omb->Component,
|
|
Omb->Description
|
|
));
|
|
|
|
Omb->Disabled = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
MsgMgr_LinkObjectWithContext(
|
|
IN PCTSTR Context,
|
|
IN PCTSTR Object
|
|
)
|
|
{
|
|
MYASSERT(Context);
|
|
MYASSERT(Object);
|
|
|
|
//
|
|
// Debug message
|
|
//
|
|
DEBUGMSG ((
|
|
DBG_MSGMGR,
|
|
"MsgMgr_LinkObjectWithContext: ADD\n"
|
|
" obj: '%s'\n"
|
|
" ctx: '%s'\n",
|
|
Object,
|
|
Context
|
|
));
|
|
|
|
pOmbAdd (Object, Context, TEXT(""), TEXT(""));
|
|
}
|
|
|
|
|
|
DWORD
|
|
pDfsGetFileAttributes (
|
|
IN PCTSTR Object
|
|
)
|
|
{
|
|
TCHAR RootPath[4];
|
|
DWORD Attribs;
|
|
|
|
if (!Object[0] || !Object[1] || !Object[2]) {
|
|
return INVALID_ATTRIBUTES;
|
|
}
|
|
|
|
RootPath[0] = Object[0];
|
|
RootPath[1] = Object[1];
|
|
RootPath[2] = Object[2];
|
|
RootPath[3] = 0;
|
|
|
|
if (GetDriveType (RootPath) != DRIVE_FIXED) {
|
|
DEBUGMSG ((DBG_VERBOSE, "%s is not a local path", Object));
|
|
Attribs = INVALID_ATTRIBUTES;
|
|
} else {
|
|
|
|
Attribs = GetFileAttributes (Object);
|
|
|
|
}
|
|
|
|
return Attribs;
|
|
}
|
|
|
|
//
|
|
// Function adds an Object Message Block (OMB) to the list of all OMBs.
|
|
// If the OMB doesn't refer to a context, then any OMB already in the list
|
|
// with a message for the same object, is disabled. In this way, there is
|
|
// only one message per handleable object.
|
|
//
|
|
VOID
|
|
pOmbAdd(
|
|
IN PCTSTR Object,
|
|
IN PCTSTR Context,
|
|
IN PCTSTR Component,
|
|
IN PCTSTR Description
|
|
)
|
|
{
|
|
|
|
TCHAR ObjectWackedIfDir[MAX_ENCODED_RULE];
|
|
P_OBJ_MSG_BLOCK Omb;
|
|
P_OBJ_MSG_BLOCK OmbTemp;
|
|
DWORD Attribs;
|
|
INT i;
|
|
|
|
DEBUGMSG ((
|
|
DBG_MSGMGR,
|
|
"pOmbAdd: ADD\n"
|
|
" obj: '%s'\n"
|
|
" ctx: '%s'\n"
|
|
" cmp: '%s'\n"
|
|
" msg: '%s'\n",
|
|
Object,
|
|
Context,
|
|
Component,
|
|
Description
|
|
));
|
|
|
|
//
|
|
// Make sure our copy of the key is wacked when it's a directory.
|
|
//
|
|
StringCopy(ObjectWackedIfDir, Object);
|
|
|
|
Attribs = pDfsGetFileAttributes (Object);
|
|
|
|
if (Attribs != INVALID_ATTRIBUTES && (Attribs & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
AppendWack(ObjectWackedIfDir);
|
|
}
|
|
|
|
//
|
|
// Disable any messages already received which have the same
|
|
// Object and Context.
|
|
//
|
|
|
|
for (i = 0; i < g_OmbEntries; i++) {
|
|
OmbTemp = *(g_OmbList + i);
|
|
|
|
if (StringIMatch(OmbTemp->Object, ObjectWackedIfDir) &&
|
|
StringIMatch(OmbTemp->Context, Context)
|
|
) {
|
|
|
|
OmbTemp->Disabled = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Allocate message block
|
|
//
|
|
Omb = PoolMemGetMemory(
|
|
g_MsgMgrPool,
|
|
sizeof(OBJ_MSG_BLOCK)
|
|
);
|
|
//
|
|
// Complete block
|
|
//
|
|
Omb->Disabled = FALSE;
|
|
|
|
Omb->Object = PoolMemDuplicateString(g_MsgMgrPool, ObjectWackedIfDir);
|
|
Omb->Context = PoolMemDuplicateString(g_MsgMgrPool, Context);
|
|
Omb->Component = (PTSTR) pGetMassagedComponent (Component);
|
|
|
|
if (Description != NULL) {
|
|
Omb->Description = PoolMemDuplicateString(g_MsgMgrPool, Description);
|
|
} else {
|
|
Omb->Description = NULL;
|
|
}
|
|
|
|
//
|
|
// Grow the message list if necessary
|
|
//
|
|
if (g_OmbEntries >= g_OmbEntriesMax) {
|
|
|
|
g_OmbEntriesMax += 25;
|
|
|
|
g_OmbList = MemReAlloc(
|
|
g_hHeap,
|
|
0,
|
|
g_OmbList,
|
|
g_OmbEntriesMax * sizeof(P_OBJ_MSG_BLOCK)
|
|
);
|
|
}
|
|
|
|
//
|
|
// Save block
|
|
//
|
|
*(g_OmbList + g_OmbEntries) = Omb;
|
|
|
|
//
|
|
// Bump the list size
|
|
//
|
|
g_OmbEntries++;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Function:
|
|
// 1) walks the list of deferred message entries. If an entry has no context
|
|
// and remains enabled, its Object message is printed.
|
|
// 2) walks he g_ContextMsgs table is walked. For each, g_OmbList is
|
|
// traversed; if any entry is enabled and has a matching context, the context
|
|
// message is printed.
|
|
//
|
|
VOID
|
|
pDisplayObjectMsgs (
|
|
VOID
|
|
)
|
|
{
|
|
PTSTR ComponentNameFromLink;
|
|
BOOL ComponentIsLinkTarget;
|
|
P_OBJ_MSG_BLOCK Omb;
|
|
INT i;
|
|
|
|
//
|
|
// Find entries with no context. If they are enabled: 1) print the message;
|
|
// 2) disable the entries so they will be skipped in the steps that follow.
|
|
//
|
|
for (i = 0; i < g_OmbEntries; i++) {
|
|
|
|
Omb = *(g_OmbList + i);
|
|
|
|
if (!Omb->Disabled && !(*Omb->Context)) {
|
|
|
|
//
|
|
// Print the message.
|
|
//
|
|
// Before printing, attempt to replace the ->Component string
|
|
// with one taken from a shell link, if available. This functionality,
|
|
// if expanded, could be broken into a separate function.
|
|
//
|
|
|
|
ComponentIsLinkTarget = pFindLinkTargetDescription(
|
|
Omb->Component, // component may be link target
|
|
&ComponentNameFromLink // if so, this may be more descriptive
|
|
);
|
|
|
|
if (ComponentIsLinkTarget) {
|
|
|
|
DEBUGMSG((
|
|
DBG_MSGMGR,
|
|
"MsgMgr_pResolveContextAndPrint: DISPLAYING #1\n"
|
|
" cmp: '%s'\n"
|
|
" msg: '%s'\n",
|
|
ComponentNameFromLink,
|
|
Omb->Description
|
|
));
|
|
|
|
// Use the link description
|
|
pAddBadSoftwareWrapper (
|
|
Omb->Object,
|
|
ComponentNameFromLink,
|
|
Omb->Description
|
|
);
|
|
|
|
LOG ((
|
|
LOG_INFORMATION,
|
|
(PCSTR)MSG_MSGMGR_ADD,
|
|
Omb->Object,
|
|
Omb->Description
|
|
));
|
|
|
|
} else {
|
|
|
|
DEBUGMSG((
|
|
DBG_MSGMGR,
|
|
"MsgMgr_pResolveContextAndPrint: DISPLAYING #2\n"
|
|
" obj: '%s'\n"
|
|
" cmp: '%s'\n"
|
|
" msg: '%s'\n",
|
|
Omb->Object,
|
|
Omb->Component,
|
|
Omb->Description
|
|
));
|
|
|
|
// Use Omb->Component as the description (the default case)
|
|
pAddBadSoftwareWrapper (
|
|
Omb->Object,
|
|
Omb->Component,
|
|
Omb->Description
|
|
);
|
|
|
|
LOG ((
|
|
LOG_INFORMATION,
|
|
(PCSTR)MSG_MSGMGR_ADD,
|
|
Omb->Object,
|
|
Omb->Component,
|
|
Omb->Description
|
|
));
|
|
}
|
|
|
|
//
|
|
// Disable the entry so we'll skip it in the following steps
|
|
//
|
|
Omb->Disabled = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Enumerate tabContextMsg. For each entry, look through g_OmbList to see if any
|
|
// entries that refer to it are still enabled. If there is/are any such entries,
|
|
// print the message for that context.
|
|
//
|
|
|
|
EnumHashTableWithCallback (
|
|
g_ContextMsgs,
|
|
pDisplayContextMsgs_Callback,
|
|
0
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Function enumerates the list of handled objects, and calls a function to
|
|
// supress object references which are (or are children of) the enumerated
|
|
// object. This should be called AFTER all migrate.dll's have run on the
|
|
// Win95 side.
|
|
//
|
|
static
|
|
VOID
|
|
pSuppressObjectReferences (
|
|
VOID
|
|
)
|
|
{
|
|
//
|
|
// This function disables messages in g_OmbList
|
|
//
|
|
EnumHashTableWithCallback (
|
|
g_HandledObjects,
|
|
pSuppressObjectReferences_Callback,
|
|
0
|
|
);
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
LnkTargToDescription_Add (
|
|
IN PCTSTR Target,
|
|
IN PCTSTR strDesc
|
|
)
|
|
{
|
|
PTSTR DescCopy;
|
|
|
|
// Make own copy of description
|
|
DescCopy = PoolMemDuplicateString(
|
|
g_MsgMgrPool,
|
|
strDesc
|
|
);
|
|
|
|
// Save description
|
|
HtAddStringAndData (g_LinkTargetDesc, Target, &DescCopy);
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
pFindLinkTargetDescription(
|
|
IN PCTSTR Target,
|
|
OUT PCTSTR* StrDesc
|
|
)
|
|
{
|
|
return HtFindStringAndData (g_LinkTargetDesc, Target, (PVOID) StrDesc) != 0;
|
|
}
|
|
|
|
VOID
|
|
MsgMgr_InitStringMap (
|
|
VOID
|
|
)
|
|
{
|
|
INFSTRUCT is = INITINFSTRUCT_POOLHANDLE;
|
|
PCTSTR from, to;
|
|
|
|
if (g_Win95UpgInf == INVALID_HANDLE_VALUE) {
|
|
MYASSERT (g_ToolMode);
|
|
return;
|
|
}
|
|
|
|
g_MsgMgrMap = CreateStringMapping ();
|
|
|
|
if (InfFindFirstLine (g_Win95UpgInf, S_MSG_STRING_MAPS, NULL, &is)) {
|
|
|
|
do {
|
|
|
|
from = InfGetStringField (&is, 0);
|
|
to = InfGetStringField (&is, 1);
|
|
|
|
if (from && to) {
|
|
AddStringMappingPair (g_MsgMgrMap, from, to);
|
|
}
|
|
|
|
} while (InfFindNextLine (&is));
|
|
|
|
InfCleanUpInfStruct (&is);
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
MsgMgr_EnumFirstObject (
|
|
OUT PMSGMGROBJENUM EnumPtr
|
|
)
|
|
{
|
|
EnumPtr->Index = 0;
|
|
return MsgMgr_EnumNextObject (EnumPtr);
|
|
}
|
|
|
|
BOOL
|
|
MsgMgr_EnumNextObject (
|
|
IN OUT PMSGMGROBJENUM EnumPtr
|
|
)
|
|
{
|
|
if (EnumPtr->Index >= g_OmbEntries) {
|
|
return FALSE;
|
|
}
|
|
EnumPtr->Disabled = g_OmbList[EnumPtr->Index]->Disabled;
|
|
EnumPtr->Object = g_OmbList[EnumPtr->Index]->Object;
|
|
EnumPtr->Context = g_OmbList[EnumPtr->Index]->Context;
|
|
EnumPtr->Index++;
|
|
return TRUE;
|
|
}
|