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.

825 lines
23 KiB

  1. //+-------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. //
  5. // Copyright (C) Microsoft Corporation, 2000
  6. //
  7. // File: nopin.cpp
  8. //
  9. //--------------------------------------------------------------------------
  10. #include "pch.h"
  11. #pragma hdrstop
  12. #include "eventlog.h"
  13. #include "nopin.h"
  14. #include "strings.h"
  15. #include "msg.h"
  16. //--------------------------------------------------------------------------
  17. // class CNoPinList
  18. //--------------------------------------------------------------------------
  19. CNoPinList::CNoPinList(
  20. void
  21. ) : m_pRoot(NULL)
  22. {
  23. }
  24. CNoPinList::~CNoPinList(
  25. void
  26. )
  27. {
  28. delete m_pRoot;
  29. }
  30. //
  31. // Searches the tree for a COMPLETE path that is a subpath of
  32. // pszPath. If found, pszPath specifies a file or folder
  33. // that cannot be pinned.
  34. //
  35. // Returns:
  36. // S_OK - Pinning is allowed.
  37. // S_FALSE - Pinning is NOT allowed.
  38. // NOPIN_E_BADPATH - Path is not a valid UNC.
  39. //
  40. HRESULT
  41. CNoPinList::IsPinAllowed(
  42. LPCTSTR pszPath
  43. )
  44. {
  45. TraceEnter(TRACE_UTIL, "CNoPinList::IsPinAllowed");
  46. TraceAssert(NULL != pszPath);
  47. TraceAssert(!::IsBadStringPtr(pszPath, MAX_PATH));
  48. TraceAssert(::PathIsUNC(pszPath));
  49. HRESULT hr = _Initialize();
  50. if (SUCCEEDED(hr))
  51. {
  52. hr = S_OK;
  53. //
  54. // A quick optimization is to see if the tree is empty.
  55. // If it is, any file/folder may be pinned. This helps
  56. // performance when no pinning restriction is in place.
  57. //
  58. TraceAssert(NULL != m_pRoot);
  59. if (m_pRoot->HasChildren())
  60. {
  61. if (::PathIsUNC(pszPath))
  62. {
  63. //
  64. // SubPathExists modifies the path. Need to make a local copy.
  65. //
  66. TCHAR szPath[MAX_PATH];
  67. ::lstrcpyn(szPath, pszPath, ARRAYSIZE(szPath));
  68. hr = m_pRoot->SubPathExists(szPath);
  69. if (S_FALSE == hr)
  70. {
  71. //
  72. // Absence from the tree means pinning is allowed.
  73. //
  74. hr = S_OK;
  75. }
  76. else if (S_OK == hr)
  77. {
  78. //
  79. // Presence in the tree means pinning is not allowed.
  80. //
  81. Trace((TEXT("Policy disallows pinning \"%s\""), pszPath));
  82. hr = S_FALSE;
  83. }
  84. }
  85. else
  86. {
  87. hr = NOPIN_E_BADPATH;
  88. }
  89. }
  90. }
  91. TraceAssert(S_OK == hr ||
  92. S_FALSE == hr ||
  93. NOPIN_E_BADPATH == hr);
  94. TraceLeaveResult(hr);
  95. }
  96. //
  97. // Quick check to see if ANY pin might be disallowed.
  98. // Returns:
  99. // S_OK - Tree has content.
  100. // S_FALSE - Tree is empty.
  101. //
  102. HRESULT
  103. CNoPinList::IsAnyPinDisallowed(
  104. void
  105. )
  106. {
  107. HRESULT hr = _Initialize();
  108. if (SUCCEEDED(hr))
  109. {
  110. TraceAssert(NULL != m_pRoot);
  111. hr = m_pRoot->HasChildren() ? S_OK : S_FALSE;
  112. }
  113. return hr;
  114. }
  115. //
  116. // Initializes the no-pin list by reading path strings from the
  117. // registry. The paths are stored in both HKLM and HKCU under
  118. // the following key:
  119. //
  120. // Software\Policies\Microsoft\Windows\NetCache\NoMakeAvailableOfflineList
  121. //
  122. // Path strings may contain environment variables.
  123. // Upon return the object contains a tree representing the union of all
  124. // files and folders listed in both registry keys.
  125. //
  126. // Errors in reading the registry result only in paths not being added
  127. // to the tree. No error is returned as a result of registry errors.
  128. // If an invalid UNC path is found in the registry, an even log entry
  129. // is recorded.
  130. //
  131. // Returns:
  132. // S_OK - List successfully loaded.
  133. // S_FALSE - List already initialized.
  134. // E_OUTOFMEMORY - Insufficient memory.
  135. // Other errors are possible.
  136. //
  137. HRESULT
  138. CNoPinList::_Initialize(
  139. void
  140. )
  141. {
  142. TraceEnter(TRACE_UTIL, "CNoPinList::_Initialize");
  143. HRESULT hr = S_OK;
  144. if (NULL != m_pRoot)
  145. {
  146. //
  147. // List is already initialized.
  148. //
  149. hr = S_FALSE;
  150. }
  151. else
  152. {
  153. m_pRoot = new CNode;
  154. if (NULL == m_pRoot)
  155. {
  156. hr = E_OUTOFMEMORY;
  157. }
  158. else
  159. {
  160. const HKEY rghkeyRoot[] = { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE };
  161. TCHAR szKey[MAX_PATH];
  162. ::wnsprintf(szKey,
  163. ARRAYSIZE(szKey),
  164. TEXT("%s\\%s"),
  165. REGSTR_KEY_OFFLINEFILESPOLICY,
  166. REGSTR_SUBKEY_NOMAKEAVAILABLEOFFLINELIST);
  167. for (int i = 0; SUCCEEDED(hr) && i < ARRAYSIZE(rghkeyRoot); i++)
  168. {
  169. HKEY hkey;
  170. LONG lResult = ::RegOpenKeyEx(rghkeyRoot[i],
  171. szKey,
  172. 0,
  173. KEY_QUERY_VALUE,
  174. &hkey);
  175. if (ERROR_SUCCESS == lResult)
  176. {
  177. TCHAR szName[MAX_PATH];
  178. DWORD dwIndex = 0;
  179. DWORD cchName = ARRAYSIZE(szName);
  180. //
  181. // Enumerate the paths listed in the registry.
  182. //
  183. while (SUCCEEDED(hr) &&
  184. ERROR_SUCCESS == ::RegEnumValue(hkey,
  185. dwIndex,
  186. szName,
  187. &cchName,
  188. NULL,
  189. NULL,
  190. NULL,
  191. NULL))
  192. {
  193. //
  194. // Install the path string from the registry into the
  195. // tree. This function will expand any embedded environment strings
  196. // as well as convert mapped drive specs to remote UNC paths.
  197. //
  198. hr = _InitPathFromRegistry(szName);
  199. if (NOPIN_E_BADPATH == hr)
  200. {
  201. //
  202. // This is a special error. It means someone has
  203. // put bad data into the registry. "Bad" meaning
  204. // that the path is not or does not expand to a valid UNC
  205. // path string.
  206. // Write an event log entry to tell the admin. The
  207. // entry is generated at event logging level 1. I don't want
  208. // it filling up an event log under normal conditions but
  209. // I want an admin to figure it out in case their no-pin
  210. // policy appears to be not working.
  211. //
  212. // The MSG template is this (english):
  213. //
  214. // "The registry value '%1' in key '%2\%3' is not, or does not
  215. // expand to, a valid UNC path."
  216. //
  217. // We handle the error here because this is where we still have the
  218. // value read from the registry. We include that in the event
  219. // log entry so the admin can easily find it.
  220. //
  221. CscuiEventLog log;
  222. log.Push(szName);
  223. if (HKEY_LOCAL_MACHINE == rghkeyRoot[i])
  224. {
  225. log.Push(TEXT("HKEY_LOCAL_MACHINE"));
  226. }
  227. else
  228. {
  229. log.Push(TEXT("HKEY_CURRENT_USER"));
  230. }
  231. log.Push(szKey);
  232. log.ReportEvent(EVENTLOG_WARNING_TYPE, MSG_W_INVALID_UNCPATH_INREG, 1);
  233. //
  234. // We do not abort processing because of a bad reg value.
  235. //
  236. hr = S_OK;
  237. }
  238. cchName = ARRAYSIZE(szName);
  239. dwIndex++;
  240. }
  241. ::RegCloseKey(hkey);
  242. hkey = NULL;
  243. }
  244. }
  245. }
  246. }
  247. TraceLeaveResult(hr);
  248. }
  249. //
  250. // Given a path string read from the registry, this function expands
  251. // any embedded environment strings, converts any mapped drive letters
  252. // to their corresponding remote UNC paths and installs the resulting
  253. // path string into the tree.
  254. //
  255. HRESULT
  256. CNoPinList::_InitPathFromRegistry(
  257. LPCTSTR pszPath
  258. )
  259. {
  260. TraceEnter(TRACE_UTIL, "CNoPinList::_InitPathFromRegistry");
  261. TraceAssert(NULL != pszPath);
  262. TraceAssert(!::IsBadStringPtr(pszPath, MAX_PATH));
  263. HRESULT hr = S_OK;
  264. TCHAR szNameExp[MAX_PATH]; // Expanded name string buffer.
  265. //
  266. // Expand any embedded environment strings.
  267. //
  268. if (0 == ::ExpandEnvironmentStrings(pszPath, szNameExp, ARRAYSIZE(szNameExp)))
  269. {
  270. const DWORD dwErr = GetLastError();
  271. hr = HRESULT_FROM_WIN32(dwErr);
  272. Trace((TEXT("Error %d expanding \"%s\""), dwErr, pszPath));
  273. }
  274. if (SUCCEEDED(hr))
  275. {
  276. LPCTSTR pszUncPath = NULL;
  277. LPTSTR pszRemotePath = NULL; // Created by GetRemotePath if necessary.
  278. //
  279. // Convert a common typing mistake.
  280. // Remember, these are reg entries. They could contain most anything.
  281. //
  282. for (LPTSTR s = szNameExp; *s; s++)
  283. {
  284. if (TEXT('/') == *s)
  285. {
  286. *s = TEXT('\\');
  287. }
  288. }
  289. if (::PathIsUNC(szNameExp))
  290. {
  291. //
  292. // Path is a UNC path. We're golden.
  293. //
  294. pszUncPath = szNameExp;
  295. }
  296. else
  297. {
  298. //
  299. // Path is probably a mapped drive.
  300. // Get its remote UNC path. This API returns S_FALSE
  301. // if the remote drive is not connected or if it's a local drive.
  302. //
  303. hr = ::GetRemotePath(szNameExp, &pszRemotePath);
  304. if (SUCCEEDED(hr))
  305. {
  306. if (S_OK == hr)
  307. {
  308. pszUncPath = pszRemotePath;
  309. }
  310. else if (S_FALSE == hr)
  311. {
  312. //
  313. // Path was either to a local drive or to a net drive that
  314. // isn't connected. Either way it's an invalid drive that
  315. // won't be considered in the no-pin logic. Use the expanded
  316. // value from the registry and pass that through to AddPath()
  317. // where it will be rejected as an invalid UNC path.
  318. //
  319. TraceAssert(NULL == pszRemotePath);
  320. pszUncPath = szNameExp;
  321. hr = S_OK;
  322. }
  323. }
  324. }
  325. if (SUCCEEDED(hr))
  326. {
  327. TraceAssert(NULL != pszUncPath);
  328. TraceAssert(pszUncPath == szNameExp || pszUncPath == pszRemotePath);
  329. //
  330. // Insert the UNC path into the tree.
  331. // At this point, a path may or may not be UNC. _AddPath()
  332. // will verify this.
  333. //
  334. hr = _AddPath(pszUncPath);
  335. }
  336. if (NULL != pszRemotePath)
  337. {
  338. ::LocalFree(pszRemotePath);
  339. }
  340. }
  341. TraceLeaveResult(hr);
  342. }
  343. //
  344. // Adds a path to the tree. If this is a sub-path of an existing
  345. // path in the tree, the remainder of the existing path is removed
  346. // from the tree.
  347. //
  348. // Returns:
  349. // S_OK - Path successfully added.
  350. // E_OUTOFMEMORY - Insufficient memory.
  351. // NOPIN_E_BADPATH - Invalid path string. Not a UNC.
  352. //
  353. HRESULT
  354. CNoPinList::_AddPath(
  355. LPCTSTR pszPath
  356. )
  357. {
  358. TraceAssert(NULL != pszPath);
  359. TraceAssert(!::IsBadStringPtr(pszPath, MAX_PATH));
  360. HRESULT hr = NOPIN_E_BADPATH;
  361. if (::PathIsUNC(pszPath))
  362. {
  363. //
  364. // AddPath modifies the path. Need to make a local copy.
  365. //
  366. TraceAssert(NULL != m_pRoot);
  367. TCHAR szPath[MAX_PATH];
  368. ::lstrcpyn(szPath, pszPath, ARRAYSIZE(szPath));
  369. hr = m_pRoot->AddPath(szPath);
  370. }
  371. TraceAssert(S_OK == hr ||
  372. E_OUTOFMEMORY == hr ||
  373. NOPIN_E_BADPATH == hr);
  374. return hr;
  375. }
  376. //--------------------------------------------------------------------------
  377. // class CNoPinList::CNode
  378. //--------------------------------------------------------------------------
  379. CNoPinList::CNode::~CNode(
  380. void
  381. )
  382. {
  383. if (NULL != m_pszName)
  384. {
  385. ::LocalFree(m_pszName);
  386. }
  387. delete m_pChildren;
  388. delete m_pNext;
  389. }
  390. //
  391. // Initializes a node's name value.
  392. //
  393. HRESULT
  394. CNoPinList::CNode::Initialize(
  395. LPCTSTR pszName
  396. )
  397. {
  398. TraceAssert(NULL != pszName);
  399. TraceAssert(!::IsBadStringPtr(pszName, MAX_PATH));
  400. TraceAssert(NULL == m_pszName);
  401. HRESULT hr = E_OUTOFMEMORY;
  402. m_pszName = (LPTSTR)::LocalAlloc(LPTR, (::lstrlen(pszName) + 1) * sizeof(*pszName));
  403. if (NULL != m_pszName)
  404. {
  405. ::lstrcpy(m_pszName, pszName);
  406. hr = S_OK;
  407. }
  408. return hr;
  409. }
  410. //
  411. // Add a child, keeping the children in alphabetical
  412. // order by name. We trade a little time during creation
  413. // for the speed benefits during lookup.
  414. //
  415. void
  416. CNoPinList::CNode::_AddChild(
  417. CNode *pChild
  418. )
  419. {
  420. TraceAssert(NULL != pChild);
  421. CNode **ppNode = &m_pChildren;
  422. while(NULL != *ppNode)
  423. {
  424. CNode *pNode = *ppNode;
  425. //
  426. // Find the alphabetical insertion point.
  427. //
  428. TraceAssert(NULL != pNode->m_pszName);
  429. TraceAssert(!::IsBadStringPtr(pNode->m_pszName, MAX_PATH));
  430. TraceAssert(NULL != pChild->m_pszName);
  431. TraceAssert(!::IsBadStringPtr(pChild->m_pszName, MAX_PATH));
  432. int diff = ::lstrcmpi(pChild->m_pszName, pNode->m_pszName);
  433. if (0 == diff)
  434. {
  435. //
  436. // Child already exists. Don't allow duplicates.
  437. //
  438. return;
  439. }
  440. if (diff < 0)
  441. {
  442. //
  443. // The new child is alphabetically "greater" than the currently
  444. // visited node.
  445. // Exit the loop with ppNode pointing to the pointer variable
  446. // where we'll put the address of pChild.
  447. //
  448. break;
  449. }
  450. else
  451. {
  452. //
  453. // Advance to the next node in the list.
  454. //
  455. ppNode = &pNode->m_pNext;
  456. }
  457. }
  458. //
  459. // Insert the child.
  460. //
  461. pChild->m_pNext = *ppNode;
  462. *ppNode = pChild;
  463. }
  464. //
  465. // Locates a child node in a node's list of children.
  466. // Comparison is by node name.
  467. // Returns the address of the node if found. NULL otherwise.
  468. //
  469. CNoPinList::CNode *
  470. CNoPinList::CNode::_FindChild(
  471. LPCTSTR pszName
  472. ) const
  473. {
  474. TraceAssert(NULL != pszName);
  475. TraceAssert(!::IsBadStringPtr(pszName, MAX_PATH));
  476. CNode *pChild = NULL;
  477. for (CNode *pNode = m_pChildren; pNode; pNode = pNode->m_pNext)
  478. {
  479. //
  480. // The list is sorted alphabetically.
  481. //
  482. int diff = ::lstrcmpi(pszName, pNode->m_pszName);
  483. if (diff <= 0)
  484. {
  485. //
  486. // Either we found a match or we've passed all possible
  487. // matches.
  488. //
  489. if (0 == diff)
  490. {
  491. //
  492. // Exact match.
  493. //
  494. pChild = pNode;
  495. }
  496. break;
  497. }
  498. }
  499. return pChild;
  500. }
  501. //
  502. // Given "\\brianau1\public\bin"
  503. // Returns address of "brianau1\public\bin", with *pcchComponent == 8.
  504. //
  505. // Given "public\bin"
  506. // Returns address of "bin", with *pcchComponent == 3.
  507. //
  508. LPCTSTR
  509. CNoPinList::CNode::_FindNextPathComponent( // [static]
  510. LPCTSTR pszPath,
  511. int *pcchComponent // [optional] Can be NULL.
  512. )
  513. {
  514. TraceAssert(NULL != pszPath);
  515. TraceAssert(!::IsBadStringPtr(pszPath, MAX_PATH));
  516. LPCTSTR pszBegin = pszPath;
  517. const TCHAR CH_BS = TEXT('\\');
  518. //
  519. // Skip any leading backslashes.
  520. //
  521. while(*pszBegin && CH_BS == *pszBegin)
  522. ++pszBegin;
  523. //
  524. // Find the end of the path component.
  525. //
  526. LPCTSTR pszEnd = pszBegin;
  527. while(*pszEnd && CH_BS != *pszEnd)
  528. ++pszEnd;
  529. if (NULL != pcchComponent)
  530. {
  531. *pcchComponent = int(pszEnd - pszBegin);
  532. TraceAssert(0 <= *pcchComponent);
  533. }
  534. //
  535. // Validate the final position of the begin and end ptrs.
  536. //
  537. TraceAssert(NULL != pszBegin);
  538. TraceAssert(NULL != pszEnd);
  539. TraceAssert(pszBegin >= pszPath);
  540. TraceAssert(pszBegin <= (pszPath + lstrlen(pszPath)));
  541. TraceAssert(pszEnd >= pszPath);
  542. TraceAssert(pszEnd <= (pszPath + lstrlen(pszPath)));
  543. TraceAssert(TEXT('\\') != *pszBegin);
  544. return pszBegin;
  545. }
  546. //
  547. // Recursively adds components of a path string to the tree.
  548. //
  549. HRESULT
  550. CNoPinList::CNode::AddPath(
  551. LPTSTR pszPath
  552. )
  553. {
  554. TraceAssert(NULL != pszPath);
  555. TraceAssert(!::IsBadStringPtr(pszPath, MAX_PATH));
  556. HRESULT hr = NOPIN_E_BADPATH;
  557. if (NULL != pszPath)
  558. {
  559. hr = S_OK;
  560. int cchPart = 0;
  561. LPTSTR pszPart = (LPTSTR)_FindNextPathComponent(pszPath, &cchPart);
  562. if (*pszPart)
  563. {
  564. TCHAR chTemp = TEXT('\0');
  565. _SwapChars(&chTemp, pszPart + cchPart);
  566. CNode *pChild = _FindChild(pszPart);
  567. if (NULL != pChild)
  568. {
  569. //
  570. // Found an existing node for this part of the path.
  571. // If the node has children, give the remainder of the path
  572. // to this node for addition. If it doesn't that means
  573. // it's a leaf node and all it's children are excluded from
  574. // pinning. No reason to add any children to it.
  575. //
  576. _SwapChars(&chTemp, pszPart + cchPart);
  577. if (pChild->HasChildren())
  578. {
  579. hr = pChild->AddPath(pszPart + cchPart);
  580. }
  581. }
  582. else
  583. {
  584. //
  585. // This is a new sub-path that is not yet in the tree.
  586. //
  587. hr = E_OUTOFMEMORY;
  588. pChild = new CNode();
  589. if (NULL != pChild)
  590. {
  591. //
  592. // Initialize the new child.
  593. //
  594. hr = pChild->Initialize(pszPart);
  595. _SwapChars(&chTemp, pszPart + cchPart);
  596. if (SUCCEEDED(hr))
  597. {
  598. //
  599. // Have the new child add the remainder of
  600. // the path as it's children.
  601. //
  602. hr = pChild->AddPath(pszPart + cchPart);
  603. if (SUCCEEDED(hr))
  604. {
  605. //
  606. // Link the new child into the list of children.
  607. //
  608. _AddChild(pChild);
  609. }
  610. }
  611. if (FAILED(hr))
  612. {
  613. delete pChild;
  614. pChild = NULL;
  615. }
  616. }
  617. }
  618. }
  619. else
  620. {
  621. //
  622. // We're at the end of the path, that means we're at a leaf node
  623. // and this file or directory is excluded from pinning. If it's
  624. // a directory, all children are excluded from pinning so there's
  625. // no reason to keep any child nodes in the tree. This keeps the
  626. // tree trimmed to a minimum necessary size.
  627. //
  628. delete m_pChildren;
  629. m_pChildren = NULL;
  630. }
  631. }
  632. TraceAssert(S_OK == hr ||
  633. E_OUTOFMEMORY == hr ||
  634. NOPIN_E_BADPATH == hr);
  635. return hr;
  636. }
  637. //
  638. // Recursively determines if a given complete subpath exists for a
  639. // path string. If a match occurs at a given level in the tree,
  640. // the remainder of the path string is given to the matching node
  641. // for further searching. This process continues recursively until
  642. // we hit a leaf node in the tree or the end of the path string,
  643. // whichever occurs first.
  644. //
  645. // Returns:
  646. // S_OK - A complete path exists that is a subpath of pszPath.
  647. // S_FALSE - A complete path does not exist.
  648. //
  649. HRESULT
  650. CNoPinList::CNode::SubPathExists(
  651. LPTSTR pszPath
  652. ) const
  653. {
  654. HRESULT hr = NOPIN_E_BADPATH;
  655. if (NULL != pszPath)
  656. {
  657. hr = S_FALSE;
  658. int cchPart = 0;
  659. LPTSTR pszPart = (LPTSTR)_FindNextPathComponent(pszPath, &cchPart);
  660. if (*pszPart)
  661. {
  662. TCHAR chTemp = TEXT('\0');
  663. _SwapChars(&chTemp, pszPart + cchPart);
  664. CNode *pChild = _FindChild(pszPart);
  665. _SwapChars(&chTemp, pszPart + cchPart);
  666. if (NULL != pChild)
  667. {
  668. if (pChild->HasChildren())
  669. {
  670. hr = pChild->SubPathExists(pszPart + cchPart);
  671. }
  672. else
  673. {
  674. //
  675. // Hit a leaf node. That means that we've traversed
  676. // down a complete subpath of the path in question.
  677. // Pinning of this path is not allowed.
  678. //
  679. hr = S_OK;
  680. }
  681. }
  682. }
  683. }
  684. TraceAssert(S_OK == hr ||
  685. S_FALSE == hr ||
  686. NOPIN_E_BADPATH == hr);
  687. return hr;
  688. }
  689. #if DBG
  690. //
  691. // This function dumps the contents of a tree node and all it's decendents.
  692. // The result is an indented list of nodes in the debugger output.
  693. // Handy for debugging tree build problems.
  694. //
  695. void
  696. CNoPinList::_DumpNode(
  697. const CNoPinList::CNode *pNode,
  698. int iIndent
  699. )
  700. {
  701. CNodeInspector ni(pNode);
  702. TCHAR szText[1024] = {0};
  703. LPTSTR pszWrite = szText;
  704. for (int i = 0; i < iIndent; i++)
  705. {
  706. ::lstrcat(szText, TEXT(" "));
  707. pszWrite++;
  708. }
  709. ::OutputDebugString(TEXT("\n\r"));
  710. ::wsprintf(pszWrite, TEXT("Node Address.: 0x%08X\n\r"), pNode);
  711. ::OutputDebugString(szText);
  712. ::wsprintf(pszWrite, TEXT("Name.........: %s\n\r"), ni.NodeName() ? ni.NodeName() : TEXT("<null>"));
  713. ::OutputDebugString(szText);
  714. ::wsprintf(pszWrite, TEXT("Children.....: 0x%08X\n\r"), ni.ChildList());
  715. ::OutputDebugString(szText);
  716. ::wsprintf(pszWrite, TEXT("Next Sibling.: 0x%08X\n\r"), ni.NextSibling());
  717. ::OutputDebugString(szText);
  718. if (NULL != ni.ChildList())
  719. {
  720. _DumpNode(ni.ChildList(), iIndent + 5);
  721. }
  722. if (NULL != ni.NextSibling())
  723. {
  724. _DumpNode(ni.NextSibling(), iIndent);
  725. }
  726. }
  727. //
  728. // Dump the entire tree starting with the root.
  729. //
  730. void
  731. CNoPinList::Dump(
  732. void
  733. )
  734. {
  735. ::OutputDebugString(TEXT("\n\rDumping CNoPinList\n\r"));
  736. if (NULL != m_pRoot)
  737. {
  738. _DumpNode(m_pRoot, 0);
  739. }
  740. }
  741. #endif // DBG