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.

800 lines
20 KiB

  1. //+---------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1992 - 2000.
  5. //
  6. // File: notifmgr.hxx
  7. //
  8. // Contents: Classes to deal with registry and file system change notifications
  9. //
  10. // Classes: CCiNotify
  11. // CCiNotifyMgr
  12. //
  13. // History: 14-Jul-97 SitaramR Created from dlnotify.hxx
  14. //
  15. //----------------------------------------------------------------------------
  16. #pragma once
  17. #include <refcount.hxx>
  18. #include <regevent.hxx>
  19. #include <fatnot.hxx>
  20. #include <rcstrmhd.hxx>
  21. #include <cimbmgr.hxx>
  22. #include "scaninfo.hxx"
  23. #include "acinotfy.hxx"
  24. #include "usnlist.hxx"
  25. #include "usnmgr.hxx"
  26. class CiCat;
  27. class CCiNotifyMgr;
  28. class CClientDocStore;
  29. const LONGLONG sigCiNotify = 0x594649544F4E4943i64; // "CINOTIFY"
  30. //+---------------------------------------------------------------------------
  31. //
  32. // Class: CCiNotify
  33. //
  34. // Purpose: A single scope to watch for notifications.
  35. //
  36. // History: 1-17-96 srikants Created
  37. //
  38. //----------------------------------------------------------------------------
  39. class CCiNotify : public CGenericNotify
  40. {
  41. public:
  42. CCiNotify( CCiNotifyMgr & notifyMgr,
  43. WCHAR const * wcsScope,
  44. unsigned cwcScope,
  45. VOLUMEID volumeId,
  46. ULONGLONG const & VolumeCreationTime,
  47. ULONG VolumeSerialNumber,
  48. FILETIME const & ftLastScan,
  49. USN usn,
  50. ULONGLONG const & JournalId,
  51. BOOL fUsnTreeScan,
  52. BOOL fDeep = TRUE );
  53. ~CCiNotify();
  54. BOOL IsMatch( const WCHAR * wcsPath, ULONG len ) const;
  55. # ifdef CIEXTMODE
  56. void CiExtDump(void *ciExtSelf);
  57. # endif
  58. BOOL IsValidSig()
  59. {
  60. return sigCiNotify == _sigCiNotify;
  61. }
  62. CCiNotifyMgr & GetNotifyMgr() { return _notifyMgr; }
  63. void LokRemove()
  64. {
  65. Unlink();
  66. Close();
  67. Abort();
  68. }
  69. void Abort();
  70. virtual void DoIt();
  71. virtual void ClearNotifyEnabled();
  72. void SetLastScanTime( FILETIME const & ft )
  73. {
  74. _ftLastScan = ft;
  75. }
  76. FILETIME const & GetLastScanTime() const { return _ftLastScan; }
  77. USN Usn() { return _usn; }
  78. void SetUsn( USN usn ) { _usn = usn; }
  79. ULONGLONG const & JournalId() const { return _JournalId; }
  80. void SetJournalId( ULONGLONG const & JournalId ) { _JournalId = JournalId; }
  81. VOLUMEID VolumeId() { return _volumeId; }
  82. ULONGLONG const & VolumeCreationTime() const { return _VolumeCreationTime; }
  83. ULONG VolumeSerialNumber() const { return _VolumeSerialNumber; }
  84. void SetVolumeInfo( ULONGLONG const & VolumeCreationTime, ULONG VolumeSerialNumber )
  85. {
  86. _VolumeCreationTime = VolumeCreationTime;
  87. _VolumeSerialNumber = VolumeSerialNumber;
  88. }
  89. BOOL FUsnTreeScan() { return _fUsnTreeScan; }
  90. void InitUsnTreeScan()
  91. {
  92. _fUsnTreeScan = TRUE;
  93. _usn = 0;
  94. }
  95. void SetUsnTreeScanComplete( USN usnMax )
  96. {
  97. // bogus assert: Win4Assert( _fUsnTreeScan );
  98. _fUsnTreeScan = FALSE;
  99. _usn = usnMax;
  100. }
  101. private:
  102. const LONGLONG _sigCiNotify; // signature for debugging
  103. ULONGLONG _VolumeCreationTime;
  104. union
  105. {
  106. FILETIME _ftLastScan; // Time of the last successful scan
  107. struct
  108. {
  109. ULONGLONG _JournalId; // Usn Journal Id
  110. USN _usn; // Max usn persisted for this volumeId
  111. };
  112. };
  113. ULONG _VolumeSerialNumber;
  114. CCiNotifyMgr & _notifyMgr; // CI Notification manager
  115. VOLUMEID _volumeId; // Volume id
  116. BOOL _fUsnTreeScan; // True if a usn tree traversal is going
  117. // on this scope
  118. };
  119. //+---------------------------------------------------------------------------
  120. //
  121. // Class: CVRootNotify
  122. //
  123. // Purpose: Notification on IIS VRoot changes
  124. //
  125. // History: 1-20-96 KyleP Created
  126. //
  127. //----------------------------------------------------------------------------
  128. class CVRootNotify : public CMetaDataVPathChangeCallBack
  129. {
  130. public:
  131. CVRootNotify( CiCat & cat,
  132. CiVRootTypeEnum eType,
  133. ULONG Instance,
  134. CCiNotifyMgr & notifyMgr );
  135. ~CVRootNotify() { DisableNotification(); }
  136. SCODE CallBack( BOOL fCancel );
  137. void DisableNotification();
  138. private:
  139. CiVRootTypeEnum _type;
  140. CiCat & _cat;
  141. CCiNotifyMgr & _notifyMgr;
  142. CMetaDataMgr _mdMgr;
  143. };
  144. //+---------------------------------------------------------------------------
  145. //
  146. // Class: CRegistryScopesNotify
  147. //
  148. // Purpose: Notification on Scopes registry changes
  149. //
  150. // History: 10-16-96 dlee Created
  151. //
  152. //----------------------------------------------------------------------------
  153. class CRegistryScopesNotify : public CRegNotify
  154. {
  155. public:
  156. CRegistryScopesNotify( CiCat & cat, CCiNotifyMgr & notifyMgr );
  157. ~CRegistryScopesNotify();
  158. void DoIt();
  159. private:
  160. CiCat & _cat;
  161. CCiNotifyMgr & _notifyMgr;
  162. };
  163. //+---------------------------------------------------------------------------
  164. //
  165. // Class: CCiRegistryNotify
  166. //
  167. // Purpose: A notification class for tracking ContentIndex registry
  168. // changes.
  169. //
  170. // History: 12-12-96 srikants Created
  171. //
  172. //----------------------------------------------------------------------------
  173. class CCiRegistryNotify : public CRegNotify
  174. {
  175. public:
  176. CCiRegistryNotify( CiCat & cat, CCiNotifyMgr & notifyMgr );
  177. ~CCiRegistryNotify();
  178. void DoIt();
  179. private:
  180. CiCat & _cat;
  181. CCiNotifyMgr & _notifyMgr;
  182. };
  183. typedef class TDoubleList<CCiNotify> CCiNotifyList;
  184. typedef class TFwdListIter<CCiNotify, CCiNotifyList> CFwdCiNotifyIter;
  185. //+---------------------------------------------------------------------------
  186. //
  187. // Class: CScopeInfo
  188. //
  189. // Purpose: Class that holds a scope and the last scan time.
  190. //
  191. // History: 4-19-96 srikants Created
  192. //
  193. //----------------------------------------------------------------------------
  194. class CScopeInfo
  195. {
  196. public:
  197. CScopeInfo( XArray<WCHAR> & xPath,
  198. ULONGLONG const & VolumeCreationTime,
  199. ULONG VolumeSerialNumber,
  200. FILETIME const & ftLastScan )
  201. : _volumeId(CI_VOLID_USN_NOT_ENABLED),
  202. _VolumeCreationTime( VolumeCreationTime ),
  203. _VolumeSerialNumber( VolumeSerialNumber ),
  204. _ftLastScan( ftLastScan ),
  205. _fUsnTreeScan(FALSE)
  206. {
  207. _pwszScope = xPath.Acquire();
  208. _scanType = eNoScan;
  209. }
  210. CScopeInfo( XArray<WCHAR> & xPath,
  211. ULONGLONG const & VolumeCreationTime,
  212. ULONG VolumeSerialNumber,
  213. VOLUMEID volumeId,
  214. USN usn,
  215. ULONGLONG const & JournalId,
  216. BOOL fScan )
  217. : _volumeId(volumeId),
  218. _VolumeCreationTime( VolumeCreationTime ),
  219. _VolumeSerialNumber( VolumeSerialNumber ),
  220. _fUsnTreeScan( fScan ),
  221. _JournalId( JournalId ),
  222. _usn( usn )
  223. {
  224. _pwszScope = xPath.Acquire();
  225. _scanType = eNoScan;
  226. }
  227. CScopeInfo( WCHAR const * pwszPath,
  228. ULONGLONG const & VolumeCreationTime,
  229. ULONG VolumeSerialNumber,
  230. FILETIME const & ftLastScan )
  231. : _volumeId(CI_VOLID_USN_NOT_ENABLED),
  232. _VolumeCreationTime( VolumeCreationTime ),
  233. _VolumeSerialNumber( VolumeSerialNumber ),
  234. _ftLastScan( ftLastScan ),
  235. _fUsnTreeScan( FALSE )
  236. {
  237. Win4Assert( 0 != pwszPath );
  238. ULONG len = wcslen( pwszPath );
  239. _pwszScope = new WCHAR [len+1];
  240. RtlCopyMemory( _pwszScope, pwszPath, (len+1) * sizeof(WCHAR) );
  241. _scanType = eNoScan;
  242. }
  243. CScopeInfo( WCHAR const * pwszPath,
  244. ULONGLONG const & VolumeCreationTime,
  245. ULONG VolumeSerialNumber,
  246. VOLUMEID volumeId,
  247. USN usn,
  248. ULONGLONG const & JournalId,
  249. BOOL fScan )
  250. : _volumeId(volumeId),
  251. _VolumeCreationTime( VolumeCreationTime ),
  252. _VolumeSerialNumber( VolumeSerialNumber ),
  253. _fUsnTreeScan( fScan ),
  254. _JournalId( JournalId ),
  255. _usn( usn )
  256. {
  257. Win4Assert( 0 != pwszPath );
  258. ULONG len = wcslen( pwszPath );
  259. _pwszScope = new WCHAR [len+1];
  260. RtlCopyMemory( _pwszScope, pwszPath, (len+1) * sizeof(WCHAR) );
  261. _scanType = eNoScan;
  262. }
  263. ~CScopeInfo()
  264. {
  265. delete [] _pwszScope;
  266. }
  267. WCHAR const * GetPath() const
  268. {
  269. return _pwszScope;
  270. }
  271. WCHAR * AcquirePath()
  272. {
  273. WCHAR * pwszTemp = _pwszScope;
  274. _pwszScope = 0;
  275. return pwszTemp;
  276. }
  277. void Invalidate()
  278. {
  279. delete [] _pwszScope;
  280. _pwszScope = 0;
  281. }
  282. BOOL IsValid() const { return 0 != _pwszScope; }
  283. FILETIME const & GetLastScanTime() const { return _ftLastScan; }
  284. void SetLastScanTime( FILETIME const & ft )
  285. {
  286. _ftLastScan = ft;
  287. }
  288. VOLUMEID VolumeId() const { return _volumeId; }
  289. USN Usn() const { return _usn; }
  290. void SetUsn( USN usn ) { _usn = usn; }
  291. ULONGLONG const & JournalId() const { return _JournalId; }
  292. void SetJournalId( ULONGLONG const & JournalId ) { _JournalId = JournalId; }
  293. ULONGLONG const & VolumeCreationTime() const { return _VolumeCreationTime; }
  294. ULONG VolumeSerialNumber() const { return _VolumeSerialNumber; }
  295. void SetVolumeInfo( ULONGLONG const & VolumeCreationTime, ULONG VolumeSerialNumber )
  296. {
  297. _VolumeCreationTime = VolumeCreationTime;
  298. _VolumeSerialNumber = VolumeSerialNumber;
  299. }
  300. BOOL IsScanNeeded() const { return eNoScan != _scanType; }
  301. BOOL IsFullScan() const { return eFullScan == _scanType; }
  302. BOOL IsIncrScan() const { return eIncrScan == _scanType; }
  303. void ClearScanNeeded() { _scanType = eNoScan; }
  304. void SetScanNeeded( BOOL fFull )
  305. {
  306. if ( fFull )
  307. {
  308. _scanType = eFullScan;
  309. }
  310. else if ( eNoScan == _scanType )
  311. {
  312. _scanType = eIncrScan;
  313. }
  314. }
  315. BOOL FUsnTreeScan() const { return _fUsnTreeScan; }
  316. void InitUsnTreeScan()
  317. {
  318. _fUsnTreeScan = TRUE;
  319. _usn = 0;
  320. }
  321. void SetUsnTreeScanComplete( USN usnMax )
  322. {
  323. Win4Assert( _fUsnTreeScan );
  324. Win4Assert( _usn == 0 );
  325. _fUsnTreeScan = FALSE;
  326. _usn = usnMax;
  327. }
  328. void ResetForScans()
  329. {
  330. _volumeId = CI_VOLID_USN_NOT_ENABLED;
  331. RtlZeroMemory( &_ftLastScan, sizeof(_ftLastScan) );
  332. _scanType = eFullScan;
  333. }
  334. void ResetForUsns( VOLUMEID volId, USN const & usnStart = 0 )
  335. {
  336. Win4Assert( volId != CI_VOLID_USN_NOT_ENABLED );
  337. _volumeId = volId;
  338. _usn = usnStart;
  339. _fUsnTreeScan = TRUE;
  340. }
  341. private:
  342. CScopeInfo( CScopeInfo const & src ) { Win4Assert( !"Don't call me!" ); }
  343. enum EScanType
  344. {
  345. eNoScan,
  346. eIncrScan,
  347. eFullScan
  348. };
  349. ULONGLONG _VolumeCreationTime;
  350. union
  351. {
  352. struct
  353. {
  354. ULONGLONG _JournalId;
  355. USN _usn;
  356. };
  357. FILETIME _ftLastScan;
  358. };
  359. ULONG _VolumeSerialNumber;
  360. WCHAR * _pwszScope;
  361. VOLUMEID _volumeId;
  362. BOOL _fUsnTreeScan; // True if a usn tree traversal is going
  363. // on this scope
  364. EScanType _scanType;
  365. };
  366. DECL_DYNSTACK( CScopeInfoStack, CScopeInfo );
  367. //+---------------------------------------------------------------------------
  368. //
  369. // Class: CCiNotifyMgr
  370. //
  371. // Purpose: Manages multiple scope notifications for content index. A
  372. // single content index can have multiple scopes and each scope
  373. // needs separate notifications.
  374. //
  375. // History: 1-17-96 srikants Created
  376. //
  377. // Notes:
  378. //
  379. //----------------------------------------------------------------------------
  380. #if CIDBG==1
  381. const LONGLONG eForceNetScanInterval = 2 * 60 * 1000 * 10000; // 2 mins for testing
  382. #endif // CIDBG==1
  383. class CCiNotifyMgr
  384. {
  385. enum EEventType { eNone=0x0,
  386. eKillThread=0x1,
  387. eAddScopes=0x2,
  388. eWatchIISVRoots=0x4,
  389. eWatchRegistryScopes=0x8,
  390. eWatchCiRegistry=0x10,
  391. eUnWatchW3VRoots=0x20,
  392. eUnWatchNNTPVRoots=0x40,
  393. eUnWatchIMAPVRoots=0x80 };
  394. friend class CCiNotifyIter;
  395. public:
  396. CCiNotifyMgr( CiCat & cicat, CCiScanMgr & scanMgr );
  397. ~CCiNotifyMgr( );
  398. void Resume() { _thrNotify.Resume(); }
  399. void AddPath( CScopeInfo const & scopeInfo, BOOL & fSubScopesRemoved );
  400. BOOL RemoveScope( const WCHAR * wcsPath );
  401. BOOL IsInScope( WCHAR const * pwcsRoot );
  402. CMutexSem & GetMutex() { return _mutex; }
  403. void AddRef()
  404. {
  405. _refCount.AddRef();
  406. }
  407. void Release()
  408. {
  409. _refCount.Release();
  410. }
  411. void ProcessChanges( XPtr<CCiAsyncProcessNotify> & xWorker );
  412. void ProcessChanges( BYTE const * pbChanges, WCHAR const * pwcsRoot );
  413. CCiAsyncProcessNotify * QueryAsyncWorkItem( BYTE const * pbChanges,
  414. ULONG cbChanges,
  415. WCHAR const * pwcsRoot );
  416. void SetupScan( WCHAR const * pwcsRoot );
  417. unsigned GetUpdateCount()
  418. {
  419. CLock lock(_mutex);
  420. return _nUpdates;
  421. }
  422. void IncrementUpdateCount( ULONG nUpdates )
  423. {
  424. CLock lock(_mutex);
  425. _nUpdates += nUpdates;
  426. }
  427. void ResetUpdateCount()
  428. {
  429. CLock lock(_mutex);
  430. _nUpdates = 0;
  431. }
  432. void InitiateShutdown();
  433. void WaitForShutdown();
  434. void CancelIISVRootNotify( CiVRootTypeEnum eType );
  435. void TrackIISVRoots( BOOL fTrackW3Svc, ULONG W3SvcInstance,
  436. BOOL fTrackNNTPSvc, ULONG NNTPSvcInstance,
  437. BOOL fTrackIMAPSvc, ULONG IMAPSvcInstance );
  438. void TrackScopesInRegistry();
  439. void TrackCiRegistry();
  440. BOOL GetLastScanTime( WCHAR const * pwcsRoot, FILETIME & ft );
  441. void UpdateLastScanTimes( FILETIME const & ft, CUsnFlushInfoList & usnFlushInfoList );
  442. void ForceNetPathScansIf();
  443. CiCat & GetCatalog() { return _cicat; }
  444. BOOL IsRunning() const { return !_fAbort; }
  445. private:
  446. // methods to deal with notifications.
  447. static DWORD WINAPI NotifyThread( void * self );
  448. void _DoNotifications();
  449. void _KillThread();
  450. void _LokTellThreadToAddScope();
  451. void _LokAddScopesNoThrow();
  452. void LokWatchIISVServerNoThrow();
  453. void LokUnWatchIISVServerNoThrow( CVRootNotify * pNotify );
  454. void LokWatchRegistryScopesNoThrow();
  455. void LokWatchCiRegistryNoThrow();
  456. void _LokIncrementUpdateCount()
  457. {
  458. _nUpdates++;
  459. }
  460. void _LokForceScanNetPaths();
  461. CiCat & _cicat;
  462. CCiScanMgr & _scanMgr;
  463. CMutexSem _mutex; // Serialize access to the data.
  464. CCiNotifyList _list; // List of scopes being watched for
  465. // notifications.
  466. CRefCount _refCount;
  467. unsigned _nUpdates; // Count of updates since the last time
  468. // scantime is written.
  469. BOOL _fAbort; // Flag set to TRUE if should abort
  470. LONGLONG _ftLastNetPathScan;
  471. // Time of the last force scan on
  472. // network drives with no notifications
  473. //
  474. // Notification thread management. System executes the APC in the context
  475. // of the thread that makes the notification request.
  476. //
  477. ULONG _evtType;
  478. CEventSem _evt;
  479. CThread _thrNotify;
  480. CScopeInfoStack _stkScopes;
  481. //
  482. // For processing changes to IIS VRoots
  483. //
  484. XPtr<CVRootNotify> _xW3SvcVRootNotify;
  485. XPtr<CVRootNotify> _xNNTPSvcVRootNotify;
  486. XPtr<CVRootNotify> _xIMAPSvcVRootNotify;
  487. BOOL _fTrackW3Svc;
  488. BOOL _fTrackNNTPSvc;
  489. BOOL _fTrackIMAPSvc;
  490. ULONG _W3SvcInstance;
  491. ULONG _NNTPSvcInstance;
  492. ULONG _IMAPSvcInstance;
  493. BOOL _fIISAdminAlive;
  494. CRegistryScopesNotify * _pRegistryScopesNotify;
  495. CCiRegistryNotify * _pCiRegistryNotify;
  496. };
  497. //+---------------------------------------------------------------------------
  498. //
  499. // Class: CCiNotifyIter
  500. //
  501. // Purpose: An iterator to give successive scopes that are being watched
  502. // for notifications.
  503. //
  504. // History: 1-25-96 srikants Created
  505. //
  506. // Notes:
  507. //
  508. //----------------------------------------------------------------------------
  509. class CCiNotifyIter
  510. {
  511. public:
  512. CCiNotifyIter( CCiNotifyMgr & notifyMgr ) :
  513. _notifyMgr(notifyMgr),
  514. _lock(notifyMgr.GetMutex()),
  515. _iNotAdded(0),
  516. _stack(notifyMgr._stkScopes),
  517. _list(notifyMgr._list),
  518. _iter(_list)
  519. {
  520. _UpdateAtEnd();
  521. }
  522. BOOL AtEnd() const { return _fAtEnd; }
  523. WCHAR const * Get()
  524. {
  525. Win4Assert( !AtEnd() );
  526. if ( _iNotAdded < _stack.Count() )
  527. return _stack.Get(_iNotAdded)->GetPath();
  528. else
  529. return _iter->GetScope();
  530. }
  531. void GetLastScanTime( FILETIME & ft )
  532. {
  533. if ( _iNotAdded < _stack.Count() )
  534. RtlZeroMemory( &ft, sizeof(FILETIME) );
  535. else
  536. ft = _iter->GetLastScanTime();
  537. }
  538. VOLUMEID VolumeId()
  539. {
  540. Win4Assert( !AtEnd() );
  541. if ( _iNotAdded < _stack.Count() )
  542. return _stack.Get(_iNotAdded)->VolumeId();
  543. else
  544. return _iter->VolumeId();
  545. }
  546. ULONGLONG const & VolumeCreationTime()
  547. {
  548. Win4Assert( !AtEnd() );
  549. if ( _iNotAdded < _stack.Count() )
  550. return _stack.Get(_iNotAdded)->VolumeCreationTime();
  551. else
  552. return _iter->VolumeCreationTime();
  553. }
  554. ULONG VolumeSerialNumber()
  555. {
  556. Win4Assert( !AtEnd() );
  557. if ( _iNotAdded < _stack.Count() )
  558. return _stack.Get(_iNotAdded)->VolumeSerialNumber();
  559. else
  560. return _iter->VolumeSerialNumber();
  561. }
  562. BOOL IsDownlevelVolume()
  563. {
  564. Win4Assert( !AtEnd() );
  565. if ( _iNotAdded < _stack.Count() )
  566. return ( CI_VOLID_USN_NOT_ENABLED == _stack.Get(_iNotAdded)->VolumeId() );
  567. else
  568. return ( CI_VOLID_USN_NOT_ENABLED == _iter->VolumeId() );
  569. }
  570. USN Usn()
  571. {
  572. Win4Assert( !AtEnd() );
  573. if ( _iNotAdded < _stack.Count() )
  574. return _stack.Get(_iNotAdded)->Usn();
  575. else
  576. return _iter->Usn();
  577. }
  578. ULONGLONG const & JournalId()
  579. {
  580. Win4Assert( !AtEnd() );
  581. if ( _iNotAdded < _stack.Count() )
  582. return _stack.Get(_iNotAdded)->JournalId();
  583. else
  584. return _iter->JournalId();
  585. }
  586. BOOL FUsnTreeScan()
  587. {
  588. Win4Assert( !AtEnd() );
  589. if ( _iNotAdded < _stack.Count() )
  590. return _stack.Get(_iNotAdded)->FUsnTreeScan();
  591. else
  592. return _iter->FUsnTreeScan();
  593. }
  594. void InitUsnTreeScan()
  595. {
  596. Win4Assert( !AtEnd() );
  597. if ( _iNotAdded < _stack.Count() )
  598. _stack.Get(_iNotAdded)->InitUsnTreeScan();
  599. else
  600. _iter->InitUsnTreeScan();
  601. }
  602. void SetTreeScanComplete();
  603. void SetUsnTreeScanComplete( USN usnMax );
  604. void Advance()
  605. {
  606. Win4Assert( !AtEnd() );
  607. if ( _iNotAdded < _stack.Count() )
  608. _iNotAdded++;
  609. else
  610. _list.Advance(_iter);
  611. _UpdateAtEnd();
  612. }
  613. private:
  614. CCiNotifyMgr & _notifyMgr;
  615. CLock _lock;
  616. unsigned _iNotAdded;
  617. BOOL _fAtEnd;
  618. CScopeInfoStack & _stack;
  619. CCiNotifyList & _list;
  620. CFwdCiNotifyIter _iter;
  621. void _UpdateAtEnd()
  622. {
  623. Win4Assert( _iNotAdded <= _stack.Count() );
  624. _fAtEnd = _iNotAdded == _stack.Count() && _list.AtEnd(_iter);
  625. }
  626. };
  627. BOOL AreIdenticalPaths( WCHAR const * pwcsPath1, WCHAR const * pwcsPath2 );