Source code of Windows XP (NT5)
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.

1787 lines
43 KiB

  1. /*++
  2. Copyright (c) 1996 Microsoft Corporation
  3. Module Name:
  4. MsgMgr.c
  5. Abstract:
  6. Message Manager allows messages to be conditioned on some setup event.
  7. Messages are of two kinds:
  8. 1) Those that depend on the migration status of ONE system object
  9. -- a directory or registry key, for example.
  10. 2) Those that depend on GROUPS of objects.
  11. We're using the phrase "Handleable Object" (HO) to mean something in the
  12. Win95 system capable of having a migration status -- of being "handled."
  13. HOs can be either files, directories, or registry keys with optional value
  14. names. HOs are always stored as strings. In the case of registry keys,
  15. strings are always "encoded" to guarantee they contain only lower-ANSI
  16. printable chars.
  17. A "Conditional Message Context" (or Context) is the association between
  18. one or more HOs and a message, which will be printed if all HOs are not
  19. eventually handled.
  20. A message has two parts: a title (called "Component")
  21. describe the group of HOs and an associated message, which is to be printed
  22. if the HOs are not all marked "handled".
  23. An Object Message Block (OMB) is a structure that
  24. describes the pairing of a HO with either a Context or a message.
  25. The string table 'g_HandledObjects' records which HOs are handled.
  26. Here are the Message Manager's externally visible functions:
  27. MsgMgr_Init()
  28. Called once at the start of Win9x setup to initialize Message Manager.
  29. MsgMgr_Cleanup()
  30. Called once in Win9x setup, after software-incompatibility message have
  31. been displayed. Frees the resources owned by Message Manager.
  32. MsgMgr_ContextMsg_Add(
  33. ContextName, // Context, e.g., "Plugin[Corel][Draw]"
  34. ComponentName, // Message title, e.g., "Corel Draw"
  35. Message); // Message text, e.g., "Corel Draw doesn't..."
  36. Creates a context and message text.
  37. MsgMgr_LinkObjectWithContext(
  38. ContextName, // Context
  39. ObjectName); // HO
  40. Records the fact that the context message depends on the handled
  41. state of the HO, ObjectName.
  42. MsgMgr_ObjectMsg_Add(
  43. ObjectName, // HO, e.g., C:\\corel\draw.exe
  44. ComponentName, // Message title, e.g., "Corel Draw"
  45. Message); // Message text, e.g., "Draw.exe doesn't ..."
  46. Associates a message with a single HO.
  47. IsReportObjectHandled (Object)
  48. Checks to see if a specific object has been marked as handled.
  49. IsReportObjectIncompatible (Object)
  50. Checks to see if a specific object is in the list of incompatible objects.
  51. Implementation:
  52. Contexts are stored in StringTables. The Context name is the
  53. key; pointers to the component name and message text are in extra data.
  54. The association between HOs, on one hand, and contexts and messages on
  55. the other, is stored in a table of Object Message Blocks, or OMBs.
  56. During Win9x setup, OMBs are added, and objects are independently
  57. marked as "handled". When all info has been collected, the list of
  58. handled objects is compared with the list of OMBs. Object messages are
  59. displayed if their object has not been handled; Context messages are
  60. displayed if at least SOME of their objects have not been handled.
  61. Author:
  62. Mike Condra 20-May-1997
  63. Revision History:
  64. marcw 08-Mar-1999 Added support for handling Answer File items.
  65. jimschm 15-Jan-1999 Moved code from migdll9x.c to here (more centralized)
  66. jimschm 23-Dec-1998 Cleaned up
  67. jimschm 23-Sep-1998 Revised to use new fileops
  68. calinn 15-Jan-1997 Modified MsgMgr_ObjectMsg_Add to get a null message
  69. mikeco 24-Sep-1997 Re-enabled context message code
  70. marcw 21-Jul-1997 Added IsIncompatibleObject/IsReportObjectHandled functions.
  71. --*/
  72. #include "pch.h"
  73. #include "uip.h"
  74. #define DBG_MSGMGR "MsgMgr"
  75. #define S_MSG_STRING_MAPS TEXT("Report String Mappings")
  76. typedef struct {
  77. PCTSTR Component;
  78. PCTSTR Message;
  79. } CONTEXT_DATA, *P_CONTEXT_DATA;
  80. //
  81. // Object Message Block (OMBs). An OMB describes a Handleable Object's relation to a
  82. // message. Either the OMB itself contains a message, or it points to a context with a
  83. // message.
  84. // For Handleable Object there is at most one OMB with a message. This amounts to saying
  85. // that Handleable Objects have only one message. However, Handleable Objects may refer
  86. // to (i.e., participate in) more than one context.
  87. //
  88. typedef struct {
  89. // Flags that are set in the process of deciding when a context's message should
  90. // be displayed.
  91. BOOL Disabled;
  92. PTSTR Object;
  93. PTSTR Context;
  94. PTSTR Component;
  95. PTSTR Description;
  96. } OBJ_MSG_BLOCK, *P_OBJ_MSG_BLOCK;
  97. ////////////////////// PUBLIC INTERFACE DESCRIPTION //////////////////////////
  98. //
  99. // Defined for callers in inc\msgmgr.c
  100. //
  101. //
  102. // Function marks an object as "handled"
  103. //
  104. DWORD
  105. pDfsGetFileAttributes (
  106. IN PCTSTR Object
  107. );
  108. HASHTABLE g_ContextMsgs = NULL;
  109. HASHTABLE g_LinkTargetDesc = NULL;
  110. PVOID g_MsgMgrPool = NULL;
  111. HASHTABLE g_HandledObjects = NULL;
  112. HASHTABLE g_BlockingObjects = NULL;
  113. HASHTABLE g_ElevatedObjects = NULL;
  114. INT g_OmbEntriesMax = 0;
  115. INT g_OmbEntries = 0;
  116. P_OBJ_MSG_BLOCK *g_OmbList = NULL;
  117. BOOL g_BlockingAppFound = FALSE;
  118. PMAPSTRUCT g_MsgMgrMap = NULL;
  119. BOOL
  120. pAddBadSoftwareWrapper (
  121. IN PCTSTR Object,
  122. IN PCTSTR Component,
  123. IN PCTSTR Message
  124. )
  125. {
  126. DWORD offset;
  127. BOOL includeInShortReport = FALSE;
  128. if (HtFindString (g_BlockingObjects, Object)) {
  129. g_BlockingAppFound = TRUE;
  130. }
  131. if (HtFindString (g_ElevatedObjects, Object)) {
  132. includeInShortReport = TRUE;
  133. }
  134. //
  135. // add this info to memdb first
  136. //
  137. MemDbSetValueEx (MEMDB_CATEGORY_COMPATREPORT, MEMDB_ITEM_COMPONENTS, Component, NULL, 0, &offset);
  138. MemDbSetValueEx (MEMDB_CATEGORY_COMPATREPORT, MEMDB_ITEM_OBJECTS, Object, NULL, offset, NULL);
  139. return AddBadSoftware (Component, Message, includeInShortReport);
  140. }
  141. typedef enum {
  142. OT_FILE,
  143. OT_DIRECTORY,
  144. OT_REGISTRY,
  145. OT_INIFILE,
  146. OT_GUID,
  147. OT_USERNAME,
  148. OT_REPORT,
  149. OT_ANSWERFILE,
  150. OT_BADGUID
  151. } OBJECT_TYPE;
  152. VOID
  153. pOmbAdd(
  154. IN PCTSTR Object,
  155. IN PCTSTR Context,
  156. IN PCTSTR Component,
  157. IN PCTSTR Description
  158. );
  159. VOID
  160. pSuppressObjectReferences (
  161. VOID
  162. );
  163. VOID
  164. pDisplayObjectMsgs (
  165. VOID
  166. );
  167. BOOL
  168. pFindLinkTargetDescription(
  169. IN PCTSTR Target,
  170. OUT PCTSTR* StrDesc
  171. );
  172. BOOL
  173. IsWacked(
  174. IN PCTSTR str
  175. );
  176. BOOL
  177. pTranslateThisRoot (
  178. PCSTR UnFixedRegKey,
  179. PCSTR RootWithWack,
  180. PCSTR NewRoot,
  181. PSTR *FixedRegKey
  182. )
  183. {
  184. UINT RootByteLen;
  185. RootByteLen = ByteCountA (RootWithWack);
  186. if (StringIMatchByteCountA (RootWithWack, UnFixedRegKey, RootByteLen)) {
  187. *FixedRegKey = DuplicateTextA (UnFixedRegKey);
  188. StringCopyA (*FixedRegKey, NewRoot);
  189. StringCopyA (AppendWackA (*FixedRegKey), (PCSTR) ((PBYTE) UnFixedRegKey + RootByteLen));
  190. return TRUE;
  191. }
  192. return FALSE;
  193. }
  194. PSTR
  195. pTranslateRoots (
  196. PCSTR UnFixedRegKey
  197. )
  198. {
  199. PSTR FixedRegKey;
  200. if (pTranslateThisRoot (UnFixedRegKey, "HKEY_LOCAL_MACHINE\\", "HKLM", &FixedRegKey) ||
  201. pTranslateThisRoot (UnFixedRegKey, "HKEY_CLASSES_ROOT\\", "HKLM\\Software\\Classes", &FixedRegKey) ||
  202. pTranslateThisRoot (UnFixedRegKey, "HKCR\\", "HKLM\\Software\\Classes", &FixedRegKey) ||
  203. pTranslateThisRoot (UnFixedRegKey, "HKEY_ROOT\\", "HKR", &FixedRegKey) ||
  204. pTranslateThisRoot (UnFixedRegKey, "HKEY_CURRENT_USER\\", "HKR", &FixedRegKey) ||
  205. pTranslateThisRoot (UnFixedRegKey, "HKCU\\", "HKR", &FixedRegKey) ||
  206. pTranslateThisRoot (UnFixedRegKey, "HKEY_CURRENT_CONFIG\\", "HKLM\\System\\CurrentControlSet", &FixedRegKey) ||
  207. pTranslateThisRoot (UnFixedRegKey, "HKCC\\", "HKLM\\System\\CurrentControlSet", &FixedRegKey)
  208. ) {
  209. FreeText (UnFixedRegKey);
  210. return FixedRegKey;
  211. }
  212. return (PSTR) UnFixedRegKey;
  213. }
  214. VOID
  215. ElevateObject (
  216. IN PCTSTR Object
  217. )
  218. /*++
  219. Routine Description:
  220. ElevateObject puts a file in the elevated object table, so that
  221. it will always appear on the short version of the report summary.
  222. Arguments:
  223. Object - Specifies a caller-encoded object string
  224. Return Value:
  225. None.
  226. --*/
  227. {
  228. HtAddString (g_ElevatedObjects, Object);
  229. }
  230. VOID
  231. HandleReportObject (
  232. IN PCTSTR Object
  233. )
  234. /*++
  235. Routine Description:
  236. HandleReportObject adds a caller-encoded object string to the handled hash
  237. table. This causes any message for the object to be suppressed.
  238. Arguments:
  239. Object - Specifies a caller-encoded object string
  240. Return Value:
  241. None.
  242. --*/
  243. {
  244. HtAddString (g_HandledObjects, Object);
  245. }
  246. VOID
  247. AddBlockingObject (
  248. IN PCTSTR Object
  249. )
  250. /*++
  251. Routine Description:
  252. AddBlockingObject adds a file to be a blocking file. If this file is not handled there
  253. will be a warning box after user report page.
  254. Arguments:
  255. Object - Specifies a caller-encoded object string
  256. Return Value:
  257. None.
  258. --*/
  259. {
  260. HtAddString (g_BlockingObjects, Object);
  261. }
  262. VOID
  263. HandleObject(
  264. IN PCTSTR Object,
  265. IN PCTSTR ObjectType
  266. )
  267. /*++
  268. Routine Description:
  269. HandleObject adds a caller-encoded object string to the handled hash table,
  270. and also marks a file as handled in fileops, if Object is a file.
  271. Arguments:
  272. Object - Specifies a caller-encoded object string
  273. ObjectType - Specifies the object type (File, Directory, Registry, Report)
  274. Return Value:
  275. None.
  276. --*/
  277. {
  278. DWORD Attribs;
  279. OBJECT_TYPE Type;
  280. PTSTR p;
  281. TCHAR LongPath[MAX_TCHAR_PATH];
  282. BOOL SuppressRegistry = TRUE;
  283. CHAR IniPath[MAX_MBCHAR_PATH * 2];
  284. BOOL IniSaved;
  285. PCSTR ValueName, SectionName;
  286. TCHAR Node[MEMDB_MAX];
  287. PTSTR FixedObject;
  288. TREE_ENUM Files;
  289. DWORD attribs;
  290. if (StringIMatch (ObjectType, TEXT("File"))) {
  291. Type = OT_FILE;
  292. } else if (StringIMatch (ObjectType, TEXT("Directory"))) {
  293. Type = OT_DIRECTORY;
  294. } else if (StringIMatch (ObjectType, TEXT("Registry"))) {
  295. Type = OT_REGISTRY;
  296. } else if (StringIMatch (ObjectType, TEXT("IniFile"))) {
  297. Type = OT_INIFILE;
  298. } else if (StringIMatch (ObjectType, TEXT("GUID"))) {
  299. Type = OT_GUID;
  300. } else if (StringIMatch (ObjectType, TEXT("BADGUID"))) {
  301. Type = OT_BADGUID;
  302. } else if (StringIMatch (ObjectType, TEXT("UserName"))) {
  303. Type = OT_USERNAME;
  304. } else if (StringIMatch (ObjectType, TEXT("Report"))) {
  305. Type = OT_REGISTRY;
  306. SuppressRegistry = FALSE;
  307. } else if (StringIMatch (ObjectType, TEXT("AnswerFile"))) {
  308. Type = OT_ANSWERFILE;
  309. } else {
  310. DEBUGMSG ((DBG_ERROR, "Object %s ignored; invalid object type: %s", Object, ObjectType));
  311. return;
  312. }
  313. if (Type == OT_FILE || Type == OT_DIRECTORY) {
  314. if (!OurGetLongPathName (
  315. Object,
  316. LongPath,
  317. sizeof (LongPath) / sizeof (LongPath[0])
  318. )) {
  319. DEBUGMSG ((DBG_ERROR, "Object %s ignored; invalid path", Object));
  320. return;
  321. }
  322. Attribs = pDfsGetFileAttributes (LongPath);
  323. if (Attribs != INVALID_ATTRIBUTES && !(Attribs & FILE_ATTRIBUTE_DIRECTORY)) {
  324. //
  325. // It's got to be a file, not a directory!
  326. //
  327. DontTouchThisFile (LongPath);
  328. MarkPathAsHandled (LongPath);
  329. MarkFileForBackup (LongPath);
  330. DEBUGMSG ((DBG_MSGMGR, "Backing up %s", LongPath));
  331. } else if (Attribs != INVALID_ATTRIBUTES) {
  332. //
  333. // LongPath is a directory. If its the root, %windir%, %windir%\system or
  334. // Program Files, then ignore it.
  335. //
  336. p = _tcschr (LongPath, TEXT('\\'));
  337. if (p) {
  338. p = _tcschr (p + 1, TEXT('\\'));
  339. }
  340. if (!p) {
  341. DEBUGMSG ((DBG_ERROR, "Object %s ignored, can't handle root dirs", Object));
  342. return;
  343. }
  344. if (!StringIMatchA (LongPath, g_WinDir) &&
  345. !StringIMatchA (LongPath, g_SystemDir) &&
  346. !StringIMatchA (LongPath, g_ProgramFilesDir)
  347. ) {
  348. if (IsDriveExcluded (LongPath)) {
  349. DEBUGMSG ((DBG_WARNING, "Skipping handled dir %s because it is excluded", LongPath));
  350. } else if (!IsDriveAccessible (LongPath)) {
  351. DEBUGMSG ((DBG_WARNING, "Skipping handled dir %s because it is not accessible", LongPath));
  352. } else {
  353. //
  354. // Let's enumerate this tree and do the right thing
  355. //
  356. if (EnumFirstFileInTree (&Files, LongPath, NULL, TRUE)) {
  357. do {
  358. DontTouchThisFile (Files.FullPath);
  359. MarkPathAsHandled (Files.FullPath);
  360. //
  361. // back up file, or make sure empty dir is restored
  362. //
  363. if (g_ConfigOptions.EnableBackup != TRISTATE_NO) {
  364. if (!Files.Directory) {
  365. DEBUGMSG ((DBG_MSGMGR, "Backing up %s", Files.FullPath));
  366. MarkFileForBackup (Files.FullPath);
  367. } else {
  368. DEBUGMSG ((DBG_MSGMGR, "Preserving possible empty dir %s", Files.FullPath));
  369. attribs = Files.FindData->dwFileAttributes;
  370. if (attribs == FILE_ATTRIBUTE_DIRECTORY) {
  371. attribs = 0;
  372. }
  373. MemDbSetValueEx (
  374. MEMDB_CATEGORY_EMPTY_DIRS,
  375. Files.FullPath,
  376. NULL,
  377. NULL,
  378. attribs,
  379. NULL
  380. );
  381. }
  382. }
  383. } while (EnumNextFileInTree (&Files));
  384. }
  385. }
  386. DontTouchThisFile (LongPath);
  387. MarkPathAsHandled (LongPath);
  388. } else {
  389. DEBUGMSG ((DBG_ERROR, "Object %s ignored, can't handle big dirs", Object));
  390. return;
  391. }
  392. //
  393. // Put an ending wack on the object so it handles all subitems in the report
  394. //
  395. LongPath[MAX_TCHAR_PATH - 2] = 0;
  396. AppendPathWack (LongPath);
  397. } else {
  398. DEBUGMSG ((
  399. DBG_WARNING,
  400. "Object %s ignored; it does not exist or is not a complete local path (%s)",
  401. Object,
  402. LongPath
  403. ));
  404. return;
  405. }
  406. //
  407. // Make sure messages for the file or dir are removed
  408. //
  409. HandleReportObject (LongPath);
  410. } else if (Type == OT_REGISTRY) {
  411. if (_tcsnextc (Object) == '*') {
  412. HandleReportObject (Object);
  413. } else {
  414. if (!_tcschr (Object, '[')) {
  415. //
  416. // This reg object does not have a value
  417. //
  418. FixedObject = AllocText (SizeOfStringA (Object) + sizeof (CHAR)*2);
  419. MYASSERT (FixedObject);
  420. StringCopy (FixedObject, Object);
  421. AppendWack (FixedObject);
  422. FixedObject = pTranslateRoots (FixedObject);
  423. MYASSERT (FixedObject);
  424. //
  425. // Handle messages for the registry key and all of its subkeys
  426. //
  427. HandleReportObject (FixedObject);
  428. //
  429. // Put a star on it so the entire node is suppressed
  430. //
  431. StringCat (FixedObject, "*");
  432. } else {
  433. //
  434. // This reg object has a value
  435. //
  436. FixedObject = DuplicateText (Object);
  437. MYASSERT (FixedObject);
  438. FixedObject = pTranslateRoots (FixedObject);
  439. MYASSERT (FixedObject);
  440. HandleReportObject (FixedObject);
  441. }
  442. //
  443. // Make sure registry key is not suppressed
  444. //
  445. if (SuppressRegistry) {
  446. Suppress95Object (FixedObject);
  447. }
  448. FreeText (FixedObject);
  449. }
  450. } else if (Type == OT_GUID) {
  451. if (!IsGuid (Object, TRUE)) {
  452. DEBUGMSG ((DBG_ERROR, "Object %s ignored because it's not a GUID", Object));
  453. return;
  454. }
  455. HandleReportObject (Object);
  456. } else if (Type == OT_BADGUID) {
  457. if (!IsGuid (Object, TRUE)) {
  458. DEBUGMSG ((DBG_ERROR, "Object %s ignored because it's not a GUID", Object));
  459. return;
  460. }
  461. MemDbBuildKey (
  462. Node,
  463. MEMDB_CATEGORY_GUIDS,
  464. NULL,
  465. NULL,
  466. Object
  467. );
  468. MemDbSetValue (Node, 0);
  469. } else if (Type == OT_USERNAME) {
  470. Node[0] = TEXT('|');
  471. _tcssafecpy (Node + 1, Object, MAX_PATH);
  472. HandleReportObject (Node);
  473. } else if (Type == OT_INIFILE) {
  474. IniSaved = FALSE;
  475. ValueName = NULL;
  476. SectionName = NULL;
  477. //
  478. // Verify the INI file exists
  479. //
  480. StringCopyByteCount (IniPath, Object, sizeof (IniPath));
  481. //
  482. // Inf INI file is a path without section, then give an error
  483. //
  484. if (OurGetLongPathName (
  485. IniPath,
  486. LongPath,
  487. sizeof (LongPath) / sizeof (LongPath[0])
  488. )) {
  489. DEBUGMSG ((DBG_ERROR, "INI file object %s ignored, must have section", Object));
  490. return;
  491. }
  492. //
  493. // Get the ValueName or SectionName
  494. //
  495. p = _tcsrchr (IniPath, TEXT('\\'));
  496. if (p) {
  497. *p = 0;
  498. ValueName = p + 1;
  499. if (!OurGetLongPathName (
  500. IniPath,
  501. LongPath,
  502. sizeof (LongPath) / sizeof (LongPath[0])
  503. )) {
  504. //
  505. // IniPath does not exist, must have both ValueName and SectionName
  506. //
  507. p = _tcsrchr (IniPath, TEXT('\\'));
  508. if (p) {
  509. //
  510. // We now have both ValueName and SectionName, IniPath must point
  511. // to a valid file
  512. //
  513. *p = 0;
  514. SectionName = p + 1;
  515. if (!OurGetLongPathName (
  516. IniPath,
  517. LongPath,
  518. sizeof (LongPath) / sizeof (LongPath[0])
  519. )) {
  520. DEBUGMSG ((DBG_ERROR, "INI file object %s ignored, INI file not found", Object));
  521. return;
  522. }
  523. } else {
  524. DEBUGMSG ((DBG_ERROR, "INI file object %s ignored, bad INI file", Object));
  525. return;
  526. }
  527. } else {
  528. //
  529. // IniPath does exist, we know we only have a SectionName
  530. //
  531. SectionName = ValueName;
  532. ValueName = TEXT("*");
  533. }
  534. } else {
  535. //
  536. // No wacks in Object!!
  537. //
  538. DEBUGMSG ((DBG_ERROR, "INI file object %s ignored, bad object", Object));
  539. return;
  540. }
  541. //
  542. // Suppress the INI file settings from NT, and make sure report entries
  543. // that come from INI files are also suppressed
  544. //
  545. MemDbBuildKey (
  546. Node,
  547. MEMDB_CATEGORY_SUPPRESS_INI_MAPPINGS,
  548. IniPath,
  549. SectionName,
  550. ValueName
  551. );
  552. MemDbSetValue (Node, 0);
  553. HandleReportObject (Node);
  554. } else if (Type == OT_ANSWERFILE) {
  555. StringCopy (Node, Object);
  556. p = _tcschr (Node, TEXT('\\'));
  557. if (p) {
  558. *p = 0;
  559. ValueName = _tcsinc (p);
  560. }
  561. else {
  562. ValueName = TEXT("*");
  563. }
  564. SectionName = Node;
  565. MemDbSetValueEx (
  566. MEMDB_CATEGORY_SUPPRESS_ANSWER_FILE_SETTINGS,
  567. SectionName,
  568. ValueName,
  569. NULL,
  570. 0,
  571. NULL
  572. );
  573. }
  574. ELSE_DEBUGMSG ((DBG_WHOOPS, "Object type %u for %s not recognized.", Type, Object));
  575. }
  576. VOID
  577. MsgMgr_Init (
  578. VOID
  579. )
  580. {
  581. // Build message pool
  582. g_MsgMgrPool = PoolMemInitNamedPool ("Message Manager");
  583. // Table of handled objects
  584. g_HandledObjects = HtAlloc();
  585. // Table of blocking objects
  586. g_BlockingObjects = HtAlloc();
  587. // Table of objects to put on short summary
  588. g_ElevatedObjects = HtAlloc();
  589. // Context messages init
  590. g_ContextMsgs = HtAllocWithData (sizeof(PCTSTR));
  591. // Link-target description init
  592. g_LinkTargetDesc = HtAllocWithData (sizeof(PVOID));
  593. // Bad Software Init
  594. g_OmbEntriesMax = 25;
  595. g_OmbEntries = 0;
  596. g_OmbList = MemAlloc(
  597. g_hHeap,
  598. 0,
  599. g_OmbEntriesMax * sizeof(P_OBJ_MSG_BLOCK)
  600. );
  601. }
  602. VOID
  603. pAddStaticHandledObjects (
  604. VOID
  605. )
  606. {
  607. INFSTRUCT is = INITINFSTRUCT_POOLHANDLE;
  608. PCTSTR object;
  609. if (InfFindFirstLine (g_Win95UpgInf, TEXT("IgnoreInReport"), NULL, &is)) {
  610. do {
  611. object = InfGetStringField (&is, 0);
  612. if (object) {
  613. HandleObject (object, TEXT("Report"));
  614. }
  615. } while (InfFindNextLine (&is));
  616. InfCleanUpInfStruct (&is);
  617. }
  618. }
  619. VOID
  620. MsgMgr_Resolve (
  621. VOID
  622. )
  623. {
  624. pAddStaticHandledObjects ();
  625. pSuppressObjectReferences(); // disable references to handled objects
  626. pDisplayObjectMsgs(); // print object & context msgs & enabled object refs
  627. }
  628. VOID
  629. MsgMgr_Cleanup (
  630. VOID
  631. )
  632. {
  633. // Context message cleanup
  634. HtFree (g_ContextMsgs);
  635. g_ContextMsgs = NULL;
  636. PoolMemDestroyPool(g_MsgMgrPool);
  637. g_MsgMgrPool = NULL;
  638. // Table of blocking objects
  639. HtFree (g_BlockingObjects);
  640. g_BlockingObjects = NULL;
  641. // Table of elevated objects
  642. HtFree (g_ElevatedObjects);
  643. g_ElevatedObjects = NULL;
  644. // Table of handled objects
  645. HtFree (g_HandledObjects);
  646. g_HandledObjects = NULL;
  647. // Link description cleanup
  648. HtFree (g_LinkTargetDesc);
  649. g_LinkTargetDesc = NULL;
  650. // Object-message list. Note, entries on list are entirely
  651. // from g_MsgMgrPool.
  652. if (NULL != g_OmbList) {
  653. MemFree(g_hHeap, 0, g_OmbList);
  654. g_OmbList = NULL;
  655. }
  656. if (g_MsgMgrMap) {
  657. DestroyStringMapping (g_MsgMgrMap);
  658. }
  659. }
  660. VOID
  661. MsgMgr_ObjectMsg_Add(
  662. IN PCTSTR Object,
  663. IN PCTSTR Component,
  664. IN PCTSTR Message
  665. )
  666. {
  667. MYASSERT(Object);
  668. MYASSERT(Component);
  669. pOmbAdd(
  670. Object,
  671. TEXT(""), // context
  672. Component,
  673. Message
  674. );
  675. }
  676. PCTSTR
  677. pGetMassagedComponent (
  678. IN PCTSTR Component
  679. )
  680. {
  681. TCHAR tempBuffer[MAX_TCHAR_PATH];
  682. PCTSTR rString = NULL;
  683. if (!Component) {
  684. return NULL;
  685. }
  686. // Do string search and replacement and make own copy of the component.
  687. if (MappingSearchAndReplaceEx (
  688. g_MsgMgrMap,
  689. Component,
  690. tempBuffer,
  691. 0,
  692. NULL,
  693. sizeof (tempBuffer),
  694. STRMAP_ANY_MATCH,
  695. NULL,
  696. NULL
  697. )) {
  698. DEBUGMSG ((DBG_MSGMGR, "Mapped %s to %s.", Component, tempBuffer));
  699. rString = PoolMemDuplicateString(g_MsgMgrPool, tempBuffer);
  700. }
  701. else {
  702. rString = PoolMemDuplicateString(g_MsgMgrPool, Component);
  703. }
  704. return rString;
  705. }
  706. VOID
  707. MsgMgr_ContextMsg_Add(
  708. IN PCTSTR Context,
  709. IN PCTSTR Component,
  710. IN PCTSTR Message
  711. )
  712. {
  713. P_CONTEXT_DATA ContextData;
  714. MYASSERT(Context);
  715. MYASSERT(Component);
  716. // Get a structure to hold the componentand message string pointers
  717. ContextData = PoolMemGetMemory(g_MsgMgrPool, sizeof(CONTEXT_DATA));
  718. // Do string search and replacement and make own copy of the component.
  719. ContextData->Component = pGetMassagedComponent (Component);
  720. // Make own copy of message
  721. if (Message != NULL) {
  722. ContextData->Message = PoolMemDuplicateString(g_MsgMgrPool, Message);
  723. }
  724. else {
  725. ContextData->Message = NULL;
  726. }
  727. //
  728. // Debug message
  729. //
  730. DEBUGMSG ((
  731. DBG_MSGMGR,
  732. "MsgMgr_ContextMsg_Add\n"
  733. " obj: '%s'\n"
  734. " ctx: '%s'\n"
  735. " cmp: '%s'\n"
  736. " msg: '%s'\n",
  737. TEXT(""),
  738. Context,
  739. Component,
  740. Message ? Message : TEXT("<No message>")
  741. ));
  742. //
  743. // Save component named and message in string table
  744. //
  745. HtAddStringAndData (
  746. g_ContextMsgs,
  747. Context,
  748. &ContextData
  749. );
  750. }
  751. BOOL
  752. IsReportObjectHandled (
  753. IN PCTSTR Object
  754. )
  755. {
  756. HASHTABLE_ENUM e;
  757. PCTSTR p, q, r;
  758. PCTSTR End;
  759. PTSTR LowerCaseObject;
  760. BOOL b = FALSE;
  761. //
  762. // Check g_HandledObjects for:
  763. //
  764. // 1. An exact match
  765. // 2. The handled object is the root of Object
  766. //
  767. if (HtFindString (g_HandledObjects, Object)) {
  768. return TRUE;
  769. }
  770. //
  771. // We know the hash table stores its strings in lower case
  772. //
  773. LowerCaseObject = JoinPaths (Object, TEXT(""));
  774. _tcslwr (LowerCaseObject);
  775. __try {
  776. if (HtFindString (g_HandledObjects, LowerCaseObject)) {
  777. b = TRUE;
  778. __leave;
  779. }
  780. End = GetEndOfString (LowerCaseObject);
  781. if (EnumFirstHashTableString (&e, g_HandledObjects)) {
  782. do {
  783. p = LowerCaseObject;
  784. q = e.String;
  785. // Guard against empty hash table strings
  786. if (*q == 0) {
  787. continue;
  788. }
  789. r = NULL;
  790. //
  791. // Check for substring match
  792. //
  793. while (*q && p < End) {
  794. r = q;
  795. if (_tcsnextc (p) != _tcsnextc (q)) {
  796. break;
  797. }
  798. p = _tcsinc (p);
  799. q = _tcsinc (q);
  800. }
  801. //
  802. // We know the hash string cannot match identically, since
  803. // we checked for an exact match earlier. To have a match,
  804. // the hash string must be shorter than the object string,
  805. // it must end in a wack, and *q must point to the nul.
  806. //
  807. MYASSERT (r);
  808. if (*q == 0 && _tcsnextc (r) == TEXT('\\')) {
  809. MYASSERT (p < End);
  810. b = TRUE;
  811. __leave;
  812. }
  813. } while (EnumNextHashTableString (&e));
  814. }
  815. }
  816. __finally {
  817. FreePathString (LowerCaseObject);
  818. }
  819. return b;
  820. }
  821. BOOL
  822. IsReportObjectIncompatible (
  823. IN PCTSTR Object
  824. )
  825. {
  826. BOOL rIsIncompatible = FALSE;
  827. DWORD i;
  828. //
  829. // First, the "handled" test... Check to see if the object is in the
  830. // handled object table. If it is, then we can return FALSE.
  831. //
  832. if (!IsReportObjectHandled(Object)) {
  833. //
  834. // It wasn't in the table. Now we have to look the hard way!
  835. // Traverse the list of incompatible objects and look for one
  836. // that matches.
  837. //
  838. for (i=0; i < (DWORD) g_OmbEntries; i++) {
  839. //
  840. // If the current object in the incompatible list ends in a wack, do a
  841. // prefix match. if the current incompatible object is a prefix of Object,
  842. // then Object is incompatible.
  843. //
  844. if (IsWacked((g_OmbList[i])->Object)) {
  845. if (StringIMatchCharCount((g_OmbList[i])->Object,Object,CharCount((g_OmbList[i])->Object))) {
  846. rIsIncompatible = TRUE;
  847. }
  848. }
  849. else {
  850. //
  851. // The current object does not end in a wack. Therefore, it is necessary
  852. // to have a complete match.
  853. //
  854. if (StringIMatch((g_OmbList[i])->Object,Object)) {
  855. rIsIncompatible = TRUE;
  856. }
  857. }
  858. }
  859. }
  860. return rIsIncompatible;
  861. }
  862. BOOL
  863. pContextMsg_Find(
  864. IN PCTSTR Context,
  865. OUT PCTSTR* Component,
  866. OUT PCTSTR* Message
  867. )
  868. {
  869. P_CONTEXT_DATA ContextData;
  870. if (HtFindStringAndData (g_ContextMsgs, Context, &ContextData)) {
  871. *Component = ContextData->Component;
  872. *Message = ContextData->Message;
  873. return TRUE;
  874. }
  875. return FALSE;
  876. }
  877. BOOL
  878. IsWacked(
  879. IN PCTSTR str
  880. )
  881. {
  882. PCTSTR pWack = _tcsrchr(str,_T('\\'));
  883. return (NULL != pWack && 0 == *(pWack+1));
  884. }
  885. //
  886. // This function is called for each Handled object in HandledObject.
  887. // Objects are final or non-final, which can be known by looking for a final
  888. // wack. It is caller's responsibility to ensure that directories and registry
  889. // entries without value-names are wacked. This allows us to blow away (marked
  890. // as handled) any other object with the wacked HO as a prefix.
  891. //
  892. BOOL
  893. pDisplayContextMsgs_Callback(
  894. IN HASHTABLE stringTable,
  895. IN HASHITEM stringId,
  896. IN PCTSTR Context,
  897. IN PVOID extraData,
  898. IN UINT extraDataSize,
  899. IN LPARAM lParam
  900. )
  901. {
  902. INT i;
  903. P_OBJ_MSG_BLOCK Omb;
  904. P_CONTEXT_DATA Data = *(P_CONTEXT_DATA*)extraData;
  905. //
  906. // Debug message
  907. //
  908. DEBUGMSG ((
  909. DBG_MSGMGR,
  910. "pDisplayContextMsgs_Callback\n"
  911. " ctx: '%s'\n"
  912. " cmp: '%s'\n"
  913. " msg: '%s'\n",
  914. Context,
  915. Data->Component,
  916. Data->Message
  917. ));
  918. //
  919. // Loop through the OMBs, looking for an enabled reference with our context.
  920. // If found, print our message.
  921. //
  922. for (i = 0; i < g_OmbEntries; i++) {
  923. Omb = *(g_OmbList + i);
  924. //
  925. // If enabled and matches our context, print us
  926. //
  927. if (!Omb->Disabled && StringIMatch (Context, Omb->Context)) {
  928. //
  929. // Debug message
  930. //
  931. DEBUGMSG((
  932. DBG_MSGMGR,
  933. "pDisplayContextMsgs_Callback: DISPLAYING\n"
  934. " dsa: %d\n"
  935. " ctx: '%s'\n"
  936. " cmp: '%s'\n"
  937. " msg: '%s'\n",
  938. Omb->Disabled,
  939. Omb->Context,
  940. Data->Component,
  941. Data->Message
  942. ));
  943. pAddBadSoftwareWrapper (
  944. Omb->Object,
  945. Data->Component,
  946. Data->Message
  947. );
  948. break;
  949. }
  950. }
  951. UNREFERENCED_PARAMETER(stringTable);
  952. UNREFERENCED_PARAMETER(stringId);
  953. UNREFERENCED_PARAMETER(extraData);
  954. UNREFERENCED_PARAMETER(lParam);
  955. return TRUE;
  956. }
  957. //
  958. // This function is called for each Handled object in HandledObject.
  959. // Objects are final or non-final, which can be known by looking for a final
  960. // wack. It is caller's responsibility to ensure that directories and registry
  961. // entries without value-names are wacked. This allows us to blow away (marked
  962. // as handled) any other object with the wacked HO as a prefix.
  963. //
  964. BOOL
  965. pSuppressObjectReferences_Callback(
  966. IN HASHITEM stringTable,
  967. IN HASHTABLE stringId,
  968. IN PCTSTR HandledObject,
  969. IN PVOID extraData,
  970. IN UINT extraDataSize,
  971. IN LPARAM lParam
  972. )
  973. {
  974. INT nHandledLen;
  975. BOOL IsNonFinalNode;
  976. INT i;
  977. P_OBJ_MSG_BLOCK Omb;
  978. UNREFERENCED_PARAMETER(stringTable);
  979. UNREFERENCED_PARAMETER(stringId);
  980. UNREFERENCED_PARAMETER(extraData);
  981. UNREFERENCED_PARAMETER(lParam);
  982. //
  983. // Find whether the HO is capable of having children. This is known by looking
  984. // for a final wack.
  985. //
  986. IsNonFinalNode = IsWacked(HandledObject);
  987. //
  988. // Find how long it is (outside the following loop)
  989. //
  990. nHandledLen = ByteCount(HandledObject) - 1;
  991. //
  992. // Loop thru the list of messages. Apply one of two tests, depending on
  993. // on whether the handled object is non-final.
  994. //
  995. for (i = 0; i < g_OmbEntries; i++) {
  996. Omb = *(g_OmbList + i);
  997. // If disabled skip
  998. if (!Omb->Disabled) {
  999. if (IsNonFinalNode) {
  1000. if (StringIMatchCharCount(
  1001. Omb->Object, // key to deferred message
  1002. HandledObject, // a handled object
  1003. nHandledLen
  1004. ) && (Omb->Object[nHandledLen] == 0 || Omb->Object[nHandledLen] == '\\')) {
  1005. DEBUGMSG((
  1006. DBG_MSGMGR,
  1007. "pSuppressObjectReferences_Callback: SUPPRESSING NON-FINAL\n"
  1008. " obj: '%s'\n"
  1009. " why: '%s'\n"
  1010. " ctx: '%s'\n"
  1011. " cmp: '%s'\n"
  1012. " msg: '%s'\n",
  1013. Omb->Object,
  1014. HandledObject,
  1015. Omb->Context,
  1016. Omb->Component,
  1017. Omb->Description
  1018. ));
  1019. Omb->Disabled = TRUE;
  1020. }
  1021. } else {
  1022. //
  1023. // When the handled object is a file (not a dir), then an exact match
  1024. // must exist with Key for the message to be suppressed.
  1025. //
  1026. if (StringIMatch (Omb->Object, HandledObject)) {
  1027. DEBUGMSG((
  1028. DBG_MSGMGR, "pSuppressObjectReferences_Callback: SUPPRESSING FINAL\n"
  1029. " obj: '%s'\n"
  1030. " why: '%s'\n"
  1031. " ctx: '%s'\n"
  1032. " cmp: '%s'\n"
  1033. " msg: '%s'\n",
  1034. Omb->Object,
  1035. HandledObject,
  1036. Omb->Context,
  1037. Omb->Component,
  1038. Omb->Description
  1039. ));
  1040. Omb->Disabled = TRUE;
  1041. }
  1042. }
  1043. }
  1044. }
  1045. return TRUE;
  1046. }
  1047. VOID
  1048. MsgMgr_LinkObjectWithContext(
  1049. IN PCTSTR Context,
  1050. IN PCTSTR Object
  1051. )
  1052. {
  1053. MYASSERT(Context);
  1054. MYASSERT(Object);
  1055. //
  1056. // Debug message
  1057. //
  1058. DEBUGMSG ((
  1059. DBG_MSGMGR,
  1060. "MsgMgr_LinkObjectWithContext: ADD\n"
  1061. " obj: '%s'\n"
  1062. " ctx: '%s'\n",
  1063. Object,
  1064. Context
  1065. ));
  1066. pOmbAdd (Object, Context, TEXT(""), TEXT(""));
  1067. }
  1068. DWORD
  1069. pDfsGetFileAttributes (
  1070. IN PCTSTR Object
  1071. )
  1072. {
  1073. TCHAR RootPath[4];
  1074. DWORD Attribs;
  1075. if (!Object[0] || !Object[1] || !Object[2]) {
  1076. return INVALID_ATTRIBUTES;
  1077. }
  1078. RootPath[0] = Object[0];
  1079. RootPath[1] = Object[1];
  1080. RootPath[2] = Object[2];
  1081. RootPath[3] = 0;
  1082. if (GetDriveType (RootPath) != DRIVE_FIXED) {
  1083. DEBUGMSG ((DBG_VERBOSE, "%s is not a local path", Object));
  1084. Attribs = INVALID_ATTRIBUTES;
  1085. } else {
  1086. Attribs = GetFileAttributes (Object);
  1087. }
  1088. return Attribs;
  1089. }
  1090. //
  1091. // Function adds an Object Message Block (OMB) to the list of all OMBs.
  1092. // If the OMB doesn't refer to a context, then any OMB already in the list
  1093. // with a message for the same object, is disabled. In this way, there is
  1094. // only one message per handleable object.
  1095. //
  1096. VOID
  1097. pOmbAdd(
  1098. IN PCTSTR Object,
  1099. IN PCTSTR Context,
  1100. IN PCTSTR Component,
  1101. IN PCTSTR Description
  1102. )
  1103. {
  1104. TCHAR ObjectWackedIfDir[MAX_ENCODED_RULE];
  1105. P_OBJ_MSG_BLOCK Omb;
  1106. P_OBJ_MSG_BLOCK OmbTemp;
  1107. DWORD Attribs;
  1108. INT i;
  1109. DEBUGMSG ((
  1110. DBG_MSGMGR,
  1111. "pOmbAdd: ADD\n"
  1112. " obj: '%s'\n"
  1113. " ctx: '%s'\n"
  1114. " cmp: '%s'\n"
  1115. " msg: '%s'\n",
  1116. Object,
  1117. Context,
  1118. Component,
  1119. Description
  1120. ));
  1121. //
  1122. // Make sure our copy of the key is wacked when it's a directory.
  1123. //
  1124. StringCopy(ObjectWackedIfDir, Object);
  1125. Attribs = pDfsGetFileAttributes (Object);
  1126. if (Attribs != INVALID_ATTRIBUTES && (Attribs & FILE_ATTRIBUTE_DIRECTORY)) {
  1127. AppendWack(ObjectWackedIfDir);
  1128. }
  1129. //
  1130. // Disable any messages already received which have the same
  1131. // Object and Context.
  1132. //
  1133. for (i = 0; i < g_OmbEntries; i++) {
  1134. OmbTemp = *(g_OmbList + i);
  1135. if (StringIMatch(OmbTemp->Object, ObjectWackedIfDir) &&
  1136. StringIMatch(OmbTemp->Context, Context)
  1137. ) {
  1138. OmbTemp->Disabled = TRUE;
  1139. }
  1140. }
  1141. //
  1142. // Allocate message block
  1143. //
  1144. Omb = PoolMemGetMemory(
  1145. g_MsgMgrPool,
  1146. sizeof(OBJ_MSG_BLOCK)
  1147. );
  1148. //
  1149. // Complete block
  1150. //
  1151. Omb->Disabled = FALSE;
  1152. Omb->Object = PoolMemDuplicateString(g_MsgMgrPool, ObjectWackedIfDir);
  1153. Omb->Context = PoolMemDuplicateString(g_MsgMgrPool, Context);
  1154. Omb->Component = (PTSTR) pGetMassagedComponent (Component);
  1155. if (Description != NULL) {
  1156. Omb->Description = PoolMemDuplicateString(g_MsgMgrPool, Description);
  1157. } else {
  1158. Omb->Description = NULL;
  1159. }
  1160. //
  1161. // Grow the message list if necessary
  1162. //
  1163. if (g_OmbEntries >= g_OmbEntriesMax) {
  1164. g_OmbEntriesMax += 25;
  1165. g_OmbList = MemReAlloc(
  1166. g_hHeap,
  1167. 0,
  1168. g_OmbList,
  1169. g_OmbEntriesMax * sizeof(P_OBJ_MSG_BLOCK)
  1170. );
  1171. }
  1172. //
  1173. // Save block
  1174. //
  1175. *(g_OmbList + g_OmbEntries) = Omb;
  1176. //
  1177. // Bump the list size
  1178. //
  1179. g_OmbEntries++;
  1180. }
  1181. //
  1182. // Function:
  1183. // 1) walks the list of deferred message entries. If an entry has no context
  1184. // and remains enabled, its Object message is printed.
  1185. // 2) walks he g_ContextMsgs table is walked. For each, g_OmbList is
  1186. // traversed; if any entry is enabled and has a matching context, the context
  1187. // message is printed.
  1188. //
  1189. VOID
  1190. pDisplayObjectMsgs (
  1191. VOID
  1192. )
  1193. {
  1194. PTSTR ComponentNameFromLink;
  1195. BOOL ComponentIsLinkTarget;
  1196. P_OBJ_MSG_BLOCK Omb;
  1197. INT i;
  1198. //
  1199. // Find entries with no context. If they are enabled: 1) print the message;
  1200. // 2) disable the entries so they will be skipped in the steps that follow.
  1201. //
  1202. for (i = 0; i < g_OmbEntries; i++) {
  1203. Omb = *(g_OmbList + i);
  1204. if (!Omb->Disabled && !(*Omb->Context)) {
  1205. //
  1206. // Print the message.
  1207. //
  1208. // Before printing, attempt to replace the ->Component string
  1209. // with one taken from a shell link, if available. This functionality,
  1210. // if expanded, could be broken into a separate function.
  1211. //
  1212. ComponentIsLinkTarget = pFindLinkTargetDescription(
  1213. Omb->Component, // component may be link target
  1214. &ComponentNameFromLink // if so, this may be more descriptive
  1215. );
  1216. if (ComponentIsLinkTarget) {
  1217. DEBUGMSG((
  1218. DBG_MSGMGR,
  1219. "MsgMgr_pResolveContextAndPrint: DISPLAYING #1\n"
  1220. " cmp: '%s'\n"
  1221. " msg: '%s'\n",
  1222. ComponentNameFromLink,
  1223. Omb->Description
  1224. ));
  1225. // Use the link description
  1226. pAddBadSoftwareWrapper (
  1227. Omb->Object,
  1228. ComponentNameFromLink,
  1229. Omb->Description
  1230. );
  1231. LOG ((
  1232. LOG_INFORMATION,
  1233. (PCSTR)MSG_MSGMGR_ADD,
  1234. Omb->Object,
  1235. Omb->Description
  1236. ));
  1237. } else {
  1238. DEBUGMSG((
  1239. DBG_MSGMGR,
  1240. "MsgMgr_pResolveContextAndPrint: DISPLAYING #2\n"
  1241. " obj: '%s'\n"
  1242. " cmp: '%s'\n"
  1243. " msg: '%s'\n",
  1244. Omb->Object,
  1245. Omb->Component,
  1246. Omb->Description
  1247. ));
  1248. // Use Omb->Component as the description (the default case)
  1249. pAddBadSoftwareWrapper (
  1250. Omb->Object,
  1251. Omb->Component,
  1252. Omb->Description
  1253. );
  1254. LOG ((
  1255. LOG_INFORMATION,
  1256. (PCSTR)MSG_MSGMGR_ADD,
  1257. Omb->Object,
  1258. Omb->Component,
  1259. Omb->Description
  1260. ));
  1261. }
  1262. //
  1263. // Disable the entry so we'll skip it in the following steps
  1264. //
  1265. Omb->Disabled = TRUE;
  1266. }
  1267. }
  1268. //
  1269. // Enumerate tabContextMsg. For each entry, look through g_OmbList to see if any
  1270. // entries that refer to it are still enabled. If there is/are any such entries,
  1271. // print the message for that context.
  1272. //
  1273. EnumHashTableWithCallback (
  1274. g_ContextMsgs,
  1275. pDisplayContextMsgs_Callback,
  1276. 0
  1277. );
  1278. }
  1279. //
  1280. // Function enumerates the list of handled objects, and calls a function to
  1281. // supress object references which are (or are children of) the enumerated
  1282. // object. This should be called AFTER all migrate.dll's have run on the
  1283. // Win95 side.
  1284. //
  1285. static
  1286. VOID
  1287. pSuppressObjectReferences (
  1288. VOID
  1289. )
  1290. {
  1291. //
  1292. // This function disables messages in g_OmbList
  1293. //
  1294. EnumHashTableWithCallback (
  1295. g_HandledObjects,
  1296. pSuppressObjectReferences_Callback,
  1297. 0
  1298. );
  1299. }
  1300. VOID
  1301. LnkTargToDescription_Add (
  1302. IN PCTSTR Target,
  1303. IN PCTSTR strDesc
  1304. )
  1305. {
  1306. PTSTR DescCopy;
  1307. // Make own copy of description
  1308. DescCopy = PoolMemDuplicateString(
  1309. g_MsgMgrPool,
  1310. strDesc
  1311. );
  1312. // Save description
  1313. HtAddStringAndData (g_LinkTargetDesc, Target, &DescCopy);
  1314. }
  1315. BOOL
  1316. pFindLinkTargetDescription(
  1317. IN PCTSTR Target,
  1318. OUT PCTSTR* StrDesc
  1319. )
  1320. {
  1321. return HtFindStringAndData (g_LinkTargetDesc, Target, (PVOID) StrDesc) != 0;
  1322. }
  1323. VOID
  1324. MsgMgr_InitStringMap (
  1325. VOID
  1326. )
  1327. {
  1328. INFSTRUCT is = INITINFSTRUCT_POOLHANDLE;
  1329. PCTSTR from, to;
  1330. if (g_Win95UpgInf == INVALID_HANDLE_VALUE) {
  1331. MYASSERT (g_ToolMode);
  1332. return;
  1333. }
  1334. g_MsgMgrMap = CreateStringMapping ();
  1335. if (InfFindFirstLine (g_Win95UpgInf, S_MSG_STRING_MAPS, NULL, &is)) {
  1336. do {
  1337. from = InfGetStringField (&is, 0);
  1338. to = InfGetStringField (&is, 1);
  1339. if (from && to) {
  1340. AddStringMappingPair (g_MsgMgrMap, from, to);
  1341. }
  1342. } while (InfFindNextLine (&is));
  1343. InfCleanUpInfStruct (&is);
  1344. }
  1345. }
  1346. BOOL
  1347. MsgMgr_EnumFirstObject (
  1348. OUT PMSGMGROBJENUM EnumPtr
  1349. )
  1350. {
  1351. EnumPtr->Index = 0;
  1352. return MsgMgr_EnumNextObject (EnumPtr);
  1353. }
  1354. BOOL
  1355. MsgMgr_EnumNextObject (
  1356. IN OUT PMSGMGROBJENUM EnumPtr
  1357. )
  1358. {
  1359. if (EnumPtr->Index >= g_OmbEntries) {
  1360. return FALSE;
  1361. }
  1362. EnumPtr->Disabled = g_OmbList[EnumPtr->Index]->Disabled;
  1363. EnumPtr->Object = g_OmbList[EnumPtr->Index]->Object;
  1364. EnumPtr->Context = g_OmbList[EnumPtr->Index]->Context;
  1365. EnumPtr->Index++;
  1366. return TRUE;
  1367. }