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.

783 lines
22 KiB

  1. /*++
  2. Copyright (c) 1995 Microsoft Corporation
  3. Module Name :
  4. dirchngp.cxx
  5. Abstract:
  6. This module contains the internal directory change routines
  7. Author:
  8. Murali R. Krishnan ( MuraliK ) 16-Jan-1995
  9. --*/
  10. #include "TsunamiP.Hxx"
  11. #pragma hdrstop
  12. #include "issched.hxx"
  13. #include "dbgutil.h"
  14. #include <lonsi.hxx>
  15. //
  16. // Manifests
  17. //
  18. //
  19. // Globals
  20. //
  21. HANDLE g_hChangeWaitThread = NULL;
  22. LONG g_nTsunamiThreads = 0;
  23. //
  24. // Local prototypes
  25. //
  26. #if DBG
  27. VOID
  28. DumpCacheStructures(
  29. VOID
  30. );
  31. #endif
  32. VOID
  33. Apc_ChangeHandler(
  34. DWORD dwErrorCode,
  35. DWORD dwBytesWritten,
  36. LPOVERLAPPED lpo
  37. )
  38. {
  39. PVIRTUAL_ROOT_MAPPING pVrm;
  40. PDIRECTORY_CACHING_INFO pDci;
  41. PLIST_ENTRY pEntry;
  42. PLIST_ENTRY pNextEntry;
  43. BOOLEAN bSuccess;
  44. PCACHE_OBJECT pCache;
  45. FILE_NOTIFY_INFORMATION *pNI;
  46. LPBYTE pMax;
  47. //
  48. // The cache lock must always be taken before the root lock
  49. //
  50. EnterCriticalSection( &CacheTable.CriticalSection );
  51. EnterCriticalSection( &csVirtualRoots );
  52. pVrm = (VIRTUAL_ROOT_MAPPING *) lpo->hEvent;
  53. ASSERT( pVrm->Signature == VIRT_ROOT_SIGNATURE );
  54. ASSERT( pVrm->cRef > 0 );
  55. //
  56. // Is this item still active?
  57. //
  58. if ( pVrm->fDeleted )
  59. {
  60. IF_DEBUG( DIRECTORY_CHANGE ) {
  61. DBGPRINTF(( DBG_CONTEXT,
  62. "Got APC for root that has been removed\n" ));
  63. }
  64. DereferenceRootMapping( pVrm );
  65. goto Exit;
  66. }
  67. pDci = ( PDIRECTORY_CACHING_INFO )( pVrm + 1 );
  68. //
  69. // It's possible (though unlikely) we received a notification for the
  70. // same item that was removed then added before we did a wait on the
  71. // new item. This is the old notify event then.
  72. //
  73. if ( !pDci->fOnSystemNotifyList )
  74. {
  75. DBGPRINTF(( DBG_CONTEXT,
  76. "Old APC notification for %s\n",
  77. pVrm->pszDirectoryA ));
  78. goto Exit;
  79. }
  80. ASSERT( pVrm->fCachingAllowed );
  81. IF_DEBUG( DIRECTORY_CHANGE ) {
  82. DBGPRINTF(( DBG_CONTEXT,
  83. "Got APC thing for %s.\n",
  84. pVrm->pszDirectoryA ));
  85. }
  86. //
  87. // Convert NotifyInfo from Unicode to Ansi
  88. //
  89. if ( dwBytesWritten )
  90. {
  91. for ( pNI = &pDci->NotifyInfo,
  92. pMax = (LPBYTE)pNI + sizeof(pDci->NotifyInfo) + sizeof(pDci->szPathNameBuffer) ;; )
  93. {
  94. CHAR achAnsi[MAX_PATH+1];
  95. pNI->FileNameLength = WideCharToMultiByte( CP_ACP,
  96. WC_COMPOSITECHECK,
  97. pNI->FileName,
  98. pNI->FileNameLength / sizeof(WCHAR),
  99. achAnsi,
  100. MAX_PATH,
  101. NULL,
  102. NULL );
  103. memcpy( pNI->FileName, achAnsi, pNI->FileNameLength );
  104. if ( pNI->NextEntryOffset )
  105. {
  106. pNI = (FILE_NOTIFY_INFORMATION*)(((LPBYTE)pNI) + pNI->NextEntryOffset);
  107. }
  108. else
  109. {
  110. break;
  111. }
  112. }
  113. }
  114. for ( pEntry = pDci->listCacheObjects.Flink;
  115. pEntry != &pDci->listCacheObjects;
  116. pEntry = pNextEntry )
  117. {
  118. pNextEntry = pEntry->Flink;
  119. pCache = CONTAINING_RECORD( pEntry, CACHE_OBJECT, DirChangeList );
  120. if ( dwBytesWritten &&
  121. ( pCache->iDemux == RESERVED_DEMUX_OPEN_FILE ||
  122. pCache->iDemux == RESERVED_DEMUX_URI_INFO ) )
  123. {
  124. for ( pNI = &pDci->NotifyInfo ;; )
  125. {
  126. if ( pCache->iDemux == RESERVED_DEMUX_URI_INFO
  127. ? ( ((PW3_URI_INFO)(pCache->pbhBlob+1))->cchName - pVrm->cchDirectoryA -1 == pNI->FileNameLength &&
  128. !_memicmp( (LPSTR)pNI->FileName,
  129. ((PW3_URI_INFO)(pCache->pbhBlob+1))->pszName + pVrm->cchDirectoryA + 1,
  130. pNI->FileNameLength ) )
  131. : ( pCache->cchLength - pVrm->cchDirectoryA -1 == pNI->FileNameLength &&
  132. !_memicmp( pCache->szPath + pVrm->cchDirectoryA + 1,
  133. pNI->FileName,
  134. pNI->FileNameLength ) ) )
  135. {
  136. IF_DEBUG( DIRECTORY_CHANGE ) {
  137. DBGPRINTF(( DBG_CONTEXT,
  138. "Expired entry for: %s.\n", pCache->szPath ));
  139. }
  140. bSuccess = DeCache( pCache, FALSE );
  141. ASSERT( bSuccess );
  142. break;
  143. }
  144. if ( pNI->NextEntryOffset )
  145. {
  146. pNI = (FILE_NOTIFY_INFORMATION*)(((LPBYTE)pNI) + pNI->NextEntryOffset);
  147. }
  148. else
  149. {
  150. break;
  151. }
  152. }
  153. }
  154. else
  155. if ( pCache->iDemux != RESERVED_DEMUX_PHYSICAL_OPEN_FILE )
  156. {
  157. IF_DEBUG( DIRECTORY_CHANGE ) {
  158. DBGPRINTF(( DBG_CONTEXT,
  159. "Expired entry for: %s.\n", pCache->szPath ));
  160. }
  161. bSuccess = DeCache( pCache, FALSE );
  162. ASSERT( bSuccess );
  163. }
  164. }
  165. INC_COUNTER( pVrm->dwServiceID,
  166. FlushesFromDirChanges );
  167. if ( !pfnReadDirChangesW( pDci->hDirectoryFile,
  168. (VOID *) &pDci->NotifyInfo,
  169. sizeof( FILE_NOTIFY_INFORMATION ) +
  170. sizeof( pDci->szPathNameBuffer ),
  171. TRUE,
  172. FILE_NOTIFY_VALID_MASK &
  173. ~FILE_NOTIFY_CHANGE_LAST_ACCESS,
  174. NULL,
  175. &pDci->Overlapped, // hEvent used as context
  176. Apc_ChangeHandler ))
  177. {
  178. DBGPRINTF(( DBG_CONTEXT,
  179. "[ApchChangeHandler] ReadDirectoryChanges returned %d\n",
  180. GetLastError() ));
  181. //
  182. // Disable caching for this directory 'cause we aren't going to get any
  183. // more change notifications
  184. //
  185. pVrm->fCachingAllowed = FALSE;
  186. CLOSE_DIRECTORY_HANDLE( pDci );
  187. //
  188. // Decrement the ref-count as we'll never get an APC notification
  189. //
  190. DereferenceRootMapping( pVrm );
  191. }
  192. Exit:
  193. LeaveCriticalSection( &csVirtualRoots );
  194. LeaveCriticalSection( &CacheTable.CriticalSection );
  195. } // Apc_ChangeHandler
  196. DWORD
  197. WINAPI
  198. ChangeWaitThread(
  199. PVOID pvParam
  200. )
  201. {
  202. PLIST_ENTRY pEntry;
  203. PVIRTUAL_ROOT_MAPPING pVrm;
  204. PDIRECTORY_CACHING_INFO pDci;
  205. DWORD dwWaitResult;
  206. HANDLE ahEvents[2];
  207. InterlockedIncrement( &g_nTsunamiThreads );
  208. ahEvents[0] = g_hQuit;
  209. ahEvents[1] = g_hNewItem;
  210. do
  211. {
  212. //
  213. // Loop through the list looking for any directories which haven't
  214. // been added yet
  215. //
  216. EnterCriticalSection( &csVirtualRoots );
  217. for ( pEntry = GlobalVRootList.Flink;
  218. pEntry != &GlobalVRootList;
  219. pEntry = pEntry->Flink ) {
  220. pVrm = CONTAINING_RECORD( pEntry,
  221. VIRTUAL_ROOT_MAPPING,
  222. GlobalListEntry );
  223. pDci = ( PDIRECTORY_CACHING_INFO )( pVrm + 1 );
  224. ASSERT( pVrm->Signature == VIRT_ROOT_SIGNATURE );
  225. if ( !pDci->fOnSystemNotifyList ) {
  226. //
  227. // call change notify, this indicates we want change notifications
  228. // on this set of handles. Note the wait is a one shot deal,
  229. // once a dir has been notified, it must be readded in the
  230. // context of this thread.
  231. //
  232. IF_DEBUG( DIRECTORY_CHANGE )
  233. DBGPRINTF(( DBG_CONTEXT,
  234. "Trying to wait on %S\n",
  235. pVrm->pszDirectoryA ));
  236. //
  237. // Use the hEvent field of the overlapped structure as the
  238. // context to pass to the change handler. This is allowed
  239. // by the ReadDirectoryChanges API
  240. //
  241. pDci->Overlapped.hEvent = (HANDLE) pVrm;
  242. //
  243. // If the memory cache size is zero, don't worry about
  244. // change notifications
  245. //
  246. if ( !pfnReadDirChangesW( pDci->hDirectoryFile,
  247. (VOID *) &pDci->NotifyInfo,
  248. sizeof( FILE_NOTIFY_INFORMATION ) +
  249. sizeof( pDci->szPathNameBuffer ),
  250. TRUE,
  251. FILE_NOTIFY_VALID_MASK &
  252. ~FILE_NOTIFY_CHANGE_LAST_ACCESS,
  253. NULL,
  254. &pDci->Overlapped, // Not used
  255. Apc_ChangeHandler ))
  256. {
  257. DBGPRINTF(( DBG_CONTEXT,
  258. "[ChangeWaitThread] ReadDirectoryChanges"
  259. " returned %d\n",
  260. GetLastError() ));
  261. DBG_ASSERT( pVrm->fCachingAllowed == FALSE );
  262. if ( pDci->hDirectoryFile )
  263. {
  264. CLOSE_DIRECTORY_HANDLE( pDci );
  265. DereferenceRootMapping( pVrm );
  266. }
  267. //
  268. // We don't shrink the buffer because we use the
  269. // fOnSystemNotifyList flag which is in portion we
  270. // would want to shrink (and it doesn't give us a whole
  271. // lot).
  272. //
  273. } else {
  274. pVrm->fCachingAllowed = TRUE;
  275. }
  276. //
  277. // Indicate we've attempted to add the entry to the system
  278. // notify list. Used for new items getting processed the 1st
  279. // time
  280. //
  281. pDci->fOnSystemNotifyList = TRUE;
  282. }
  283. }
  284. LeaveCriticalSection( &csVirtualRoots );
  285. Rewait:
  286. dwWaitResult = WaitForMultipleObjectsEx( 2,
  287. ahEvents,
  288. FALSE,
  289. INFINITE,
  290. TRUE );
  291. if ( dwWaitResult == WAIT_IO_COMPLETION )
  292. {
  293. //
  294. // Nothing to do, the APC routine took care of everything
  295. //
  296. goto Rewait;
  297. }
  298. } while ( dwWaitResult == (WAIT_OBJECT_0 + 1) );
  299. ASSERT( dwWaitResult == WAIT_OBJECT_0 );
  300. //
  301. // free the handles and all the heap.
  302. //
  303. EnterCriticalSection( &csVirtualRoots );
  304. for ( pEntry = GlobalVRootList.Flink;
  305. pEntry != &GlobalVRootList;
  306. pEntry = pEntry->Flink )
  307. {
  308. pVrm = CONTAINING_RECORD(
  309. pEntry,
  310. VIRTUAL_ROOT_MAPPING,
  311. GlobalListEntry );
  312. if ( pVrm->fCachingAllowed ) {
  313. pDci = (PDIRECTORY_CACHING_INFO) (pVrm + 1);
  314. pVrm->fDeleted = TRUE;
  315. CLOSE_DIRECTORY_HANDLE( pDci );
  316. }
  317. pEntry = pEntry->Blink;
  318. RemoveEntryList( pEntry->Flink );
  319. DereferenceRootMapping( pVrm );
  320. }
  321. LeaveCriticalSection( &csVirtualRoots );
  322. InterlockedDecrement( &g_nTsunamiThreads );
  323. return( 0 );
  324. } // ChangeWaitThread
  325. BOOL
  326. TsDecacheVroot(
  327. DIRECTORY_CACHING_INFO * pDci
  328. )
  329. /*++
  330. Description:
  331. This function decouples all of the items that need to be decached
  332. from the associated tsunami data structures and derefences the
  333. cache obj. This routine used to schedule this off to a worker thread
  334. but this is no longer necessary as this only is called during vroot
  335. destruction.
  336. This routine assumes the cache table lock and virtual root lock are
  337. taken
  338. Arguments:
  339. pDci - Directory change blob that needs to have its contents decached
  340. Returns:
  341. TRUE on success and FALSE if any failure.
  342. --*/
  343. {
  344. LIST_ENTRY * pEntry;
  345. CACHE_OBJECT * pCacheTmp;
  346. while ( !IsListEmpty( &pDci->listCacheObjects ))
  347. {
  348. pEntry = pDci->listCacheObjects.Flink;
  349. pCacheTmp = CONTAINING_RECORD( pEntry, CACHE_OBJECT, DirChangeList );
  350. ASSERT( pCacheTmp->Signature == CACHE_OBJ_SIGNATURE );
  351. if ( pCacheTmp->iDemux == RESERVED_DEMUX_PHYSICAL_OPEN_FILE ) {
  352. continue;
  353. }
  354. //
  355. // Removes this cache item from all of the lists. It had better be
  356. // on the cache lists otherwise somebody is mucking with the list
  357. // without taking the lock.
  358. //
  359. if ( !RemoveCacheObjFromLists( pCacheTmp, FALSE ) ) {
  360. ASSERT( FALSE );
  361. continue;
  362. }
  363. TsDereferenceCacheObj( pCacheTmp, FALSE );
  364. }
  365. return TRUE;
  366. } // TsDecacheVroot
  367. VOID
  368. TsFlushTimedOutCacheObjects(
  369. VOID
  370. )
  371. /*++
  372. Description:
  373. This function walks all cache objects and decrements the TTL of the object.
  374. When the TTL reaches zero, the object is removed from the cache.
  375. --*/
  376. {
  377. LIST_ENTRY * pEntry;
  378. LIST_ENTRY * pNextEntry;
  379. CACHE_OBJECT * pCacheTmp;
  380. LIST_ENTRY ListHead;
  381. InitializeListHead( &ListHead );
  382. EnterCriticalSection( &CacheTable.CriticalSection );
  383. EnterCriticalSection( &csVirtualRoots );
  384. #if DBG
  385. IF_DEBUG( CACHE ) {
  386. DumpCacheStructures();
  387. }
  388. #endif
  389. for ( pEntry = CacheTable.MruList.Flink;
  390. pEntry != &CacheTable.MruList;
  391. pEntry = pNextEntry ) {
  392. pNextEntry = pEntry->Flink;
  393. pCacheTmp = CONTAINING_RECORD( pEntry, CACHE_OBJECT, MruList );
  394. ASSERT( pCacheTmp->Signature == CACHE_OBJ_SIGNATURE );
  395. if ( pCacheTmp->iDemux == RESERVED_DEMUX_PHYSICAL_OPEN_FILE ) {
  396. continue;
  397. }
  398. //
  399. // If the object hasn't been referenced since the last TTL, throw
  400. // it out now
  401. //
  402. if ( pCacheTmp->TTL == 0 ) {
  403. //
  404. // Removes this cache item from all of the lists. It had better be
  405. // on the cache lists otherwise somebody is mucking with the list
  406. // without taking the lock. We put it on a temporary list that
  407. // we'll traverse after we release the locks
  408. //
  409. if ( !RemoveCacheObjFromLists( pCacheTmp, FALSE ) ) {
  410. ASSERT( FALSE );
  411. continue;
  412. }
  413. InsertTailList( &ListHead, &pCacheTmp->DirChangeList );
  414. } else {
  415. pCacheTmp->TTL--;
  416. }
  417. }
  418. LeaveCriticalSection( &csVirtualRoots );
  419. LeaveCriticalSection( &CacheTable.CriticalSection );
  420. //
  421. // Now do the dereferences which may actually close the objects now that
  422. // we don't have to hold the locks
  423. //
  424. for ( pEntry = ListHead.Flink;
  425. pEntry != &ListHead;
  426. pEntry = pNextEntry ) {
  427. pNextEntry = pEntry->Flink;
  428. pCacheTmp = CONTAINING_RECORD( pEntry, CACHE_OBJECT, DirChangeList );
  429. ASSERT( pCacheTmp->Signature == CACHE_OBJ_SIGNATURE );
  430. TsDereferenceCacheObj( pCacheTmp, TRUE );
  431. }
  432. } // TsFlushTimedOutCacheObjects
  433. #if DBG
  434. VOID
  435. DumpCacheStructures(
  436. VOID
  437. )
  438. {
  439. LIST_ENTRY * pEntry;
  440. DWORD cItemsOnBin = 0;
  441. DWORD cTotalItems = 0;
  442. DWORD i, c;
  443. DBGPRINTF(( DBG_CONTEXT,
  444. "[DumpCacheStructures] CacheTable at 0x%lx, MAX_BINS = %d\n",
  445. &CacheTable,
  446. MAX_BINS ));
  447. for ( i = 0; i < MAX_BINS; i++ )
  448. {
  449. for ( pEntry = CacheTable.Items[i].Flink, cItemsOnBin = 0;
  450. pEntry != &CacheTable.Items[i];
  451. pEntry = pEntry->Flink, cItemsOnBin++, cTotalItems++ )
  452. {
  453. ;
  454. }
  455. if ( cItemsOnBin > 0) {
  456. DBGPRINTF(( DBG_CONTEXT,
  457. "Bin[%3d] %4d\n",
  458. i,
  459. cItemsOnBin ));
  460. }
  461. }
  462. DBGPRINTF(( DBG_CONTEXT,
  463. "Total Objects in bins: %d\n",
  464. cTotalItems ));
  465. DBGPRINTF(( DBG_CONTEXT,
  466. "=====================================================\n" ));
  467. //
  468. // Now print the contents of each bin
  469. //
  470. for ( i = 0; i < MAX_BINS; i++ )
  471. {
  472. PCACHE_OBJECT pcobj;
  473. if ( IsListEmpty( &CacheTable.Items[i] ))
  474. continue;
  475. DBGPRINTF(( DBG_CONTEXT,
  476. "================== Bin %d ==================\n",
  477. i ));
  478. for ( pEntry = CacheTable.Items[i].Flink, cItemsOnBin = 0;
  479. pEntry != &CacheTable.Items[i];
  480. pEntry = pEntry->Flink, cItemsOnBin++ )
  481. {
  482. pcobj = CONTAINING_RECORD( pEntry, CACHE_OBJECT, BinList );
  483. DBGPRINTF(( DBG_CONTEXT,
  484. "CACHE_OBJECT[0x%lx] Service = %d, Instance = %d iDemux = 0x%lx ref = %d, TTL = %d\n"
  485. " hash = 0x%lx, cchLength = %d\n"
  486. " %s\n",
  487. pcobj,
  488. pcobj->dwService,
  489. pcobj->dwInstance,
  490. pcobj->iDemux,
  491. pcobj->references,
  492. pcobj->TTL,
  493. pcobj->hash,
  494. pcobj->cchLength,
  495. pcobj->szPath ));
  496. }
  497. }
  498. }
  499. #endif //DBG
  500. VOID
  501. TsDumpCacheToHtml( OUT CHAR * pchBuffer, IN OUT LPDWORD lpcbBuffer )
  502. {
  503. LIST_ENTRY * pEntry;
  504. DWORD cItemsOnBin = 0;
  505. DWORD cTotalItems = 0;
  506. DWORD i, c, cb;
  507. EnterCriticalSection( &CacheTable.CriticalSection );
  508. EnterCriticalSection( &csVirtualRoots );
  509. DBG_ASSERT( lpcbBuffer != NULL && pchBuffer != NULL);
  510. DBG_ASSERT( *lpcbBuffer > 10240);
  511. cb = wsprintf( pchBuffer,
  512. " CacheTable at 0x%lx, MAX_BINS=%d<br>"
  513. "<TABLE BORDER> <TR> <TH> Bin Number </TH> "
  514. // " <TH> # Items </TH> "
  515. ,
  516. &CacheTable, MAX_BINS);
  517. //
  518. // Generate the column headings for the bins
  519. // 0, 1, 2, ..... 9
  520. //
  521. for (i = 0; i < 10; i++) {
  522. if ( *lpcbBuffer >= cb + 20) {
  523. cb += wsprintf( pchBuffer +cb,
  524. "<TH>%d</TH>",
  525. i);
  526. }
  527. } // for
  528. for ( i = 0; i < MAX_BINS; i++ ) {
  529. for ( pEntry = CacheTable.Items[i].Flink, cItemsOnBin = 0;
  530. pEntry != &CacheTable.Items[i];
  531. pEntry = pEntry->Flink, cItemsOnBin++ )
  532. {
  533. // count the number of items in this bin
  534. ;
  535. }
  536. cTotalItems += cItemsOnBin;
  537. if ( *lpcbBuffer >= cb + 60) {
  538. if ( i % 10 == 0) {
  539. //
  540. // start a new row
  541. //
  542. cb += wsprintf( pchBuffer + cb,
  543. "</TR><TR><TH>[%3d] </TH>",
  544. i);
  545. }
  546. if ( cItemsOnBin > 0) {
  547. cb += wsprintf( pchBuffer + cb,
  548. "<TD>%4d</TD>",
  549. cItemsOnBin);
  550. } else {
  551. //
  552. // Dump nothing for zero slots
  553. //
  554. cb += wsprintf( pchBuffer + cb,
  555. "<TD><font color=\"0x80808080\"> </font></TD>",
  556. cItemsOnBin);
  557. }
  558. }
  559. } // for all bins
  560. if ( *lpcbBuffer >= cb + 50) {
  561. cb += wsprintf( pchBuffer + cb,
  562. "</TR></TABLE><p>"
  563. "Total Objects in bins: %d; OpenFilesInUse(%d);"
  564. " Max Allowed=%d. <br> <hr>"
  565. " The cached objects: "
  566. ,
  567. cTotalItems,
  568. CacheTable.OpenFileInUse,
  569. CacheTable.MaxOpenFileInUse
  570. );
  571. }
  572. //
  573. // Now print the contents of each bin
  574. //
  575. for ( i = 0; i < MAX_BINS; i++ ) {
  576. PCACHE_OBJECT pcobj;
  577. if ( IsListEmpty( &CacheTable.Items[i] ))
  578. continue;
  579. if ( *lpcbBuffer >= cb + 60) {
  580. cb += wsprintf( pchBuffer + cb,
  581. "<hr><b>============ Bin %d ==========</b><br>",
  582. i );
  583. }
  584. for ( pEntry = CacheTable.Items[i].Flink, cItemsOnBin = 0;
  585. pEntry != &CacheTable.Items[i];
  586. pEntry = pEntry->Flink, cItemsOnBin++ )
  587. {
  588. pcobj = CONTAINING_RECORD( pEntry, CACHE_OBJECT, BinList );
  589. if ( *lpcbBuffer >= cb + 300) {
  590. cb += wsprintf( pchBuffer + cb,
  591. "[0x%lx] Svc:Inst = %d:%d; "
  592. "iDemux=0x%lx; ref=%d; TTL=%d; "
  593. " hash=0x%lx; "
  594. " (%d) %s<br>",
  595. pcobj,
  596. pcobj->dwService,
  597. pcobj->dwInstance,
  598. pcobj->iDemux,
  599. pcobj->references,
  600. pcobj->TTL,
  601. pcobj->hash,
  602. pcobj->cchLength,
  603. pcobj->szPath
  604. );
  605. }
  606. } // for each item in bin
  607. } // for each bin
  608. LeaveCriticalSection( &csVirtualRoots );
  609. LeaveCriticalSection( &CacheTable.CriticalSection );
  610. *lpcbBuffer = cb;
  611. return;
  612. } // TsDumpCacheToHtml()