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.

830 lines
22 KiB

  1. /*++
  2. Copyright (c) 1995 Microsoft Corporation
  3. Module Name :
  4. getdirp.cxx
  5. Abstract:
  6. This module implements the functions for getting directory listings
  7. and transparently caching them.
  8. ( This uses OS specific functions to obtain the directory).
  9. Author:
  10. Murali R. Krishnan ( MuraliK ) 13-Jan-1995
  11. Project:
  12. Tsunami Lib
  13. ( Common caching and directory functions for Internet Services)
  14. Functions Exported:
  15. BOOL TsGetDirectoryListingA()
  16. BOOL TsFreeDirectoryListing()
  17. int __cdecl
  18. AlphaCompareFileBothDirInfo(
  19. IN const void * pvFileInfo1,
  20. IN const void * pvFileInfo2)
  21. TS_DIRECTORY_HEADER::ReadFromNtDirectoryFile(
  22. IN LPCWSTR pwszDirectoryName
  23. )
  24. TS_DIRECTORY_HEADER::BuildInfoPointers(
  25. IN LPCWSTR pwszDirectoryName
  26. )
  27. TS_DIRECTORY_HEADER::CleanupThis()
  28. Revision History:
  29. --*/
  30. /************************************************************
  31. * Include Headers
  32. ************************************************************/
  33. # include "tsunamip.hxx"
  34. # include <stdlib.h>
  35. # include <string.h>
  36. # include <dbgutil.h>
  37. /************************************************************
  38. * Type Definitions
  39. ************************************************************/
  40. #define DIRECTORY_BUFFER_SIZE 8160 /* < 8192 bytes */
  41. /************************************************************
  42. * Functions
  43. ************************************************************/
  44. BOOL FreeDirectoryHeaderContents( PVOID pvOldBlock );
  45. dllexp
  46. BOOL
  47. TsGetDirectoryListing(
  48. IN const TSVC_CACHE &tsCache,
  49. IN PCSTR pszDirectoryName,
  50. IN HANDLE ListingUser,
  51. OUT PTS_DIRECTORY_HEADER * ppTsDirectoryHeader
  52. )
  53. /*++
  54. This function obtains the directory listing for dir specified
  55. in pszDirectoryName.
  56. Arguments:
  57. tsCache Cache structure which is used for lookup
  58. pwszDirectoryName pointer to string containing the directory name
  59. ListingUser Handle for the user opening the directory
  60. ppTsDirectoryHeader
  61. pointer to pointer to class containing directory information.
  62. Filled on successful return. On failure this will be NULL
  63. Returns:
  64. TRUE on success and FALSE if there is a failure.
  65. --*/
  66. {
  67. ASSERT( tsCache.IsValid() );
  68. ASSERT( pszDirectoryName != NULL );
  69. ASSERT( ppTsDirectoryHeader != NULL);
  70. PVOID pvBlob = NULL;
  71. ULONG ulSize = 0;
  72. BOOL bSuccess;
  73. //
  74. // First, check to see if we have already cached a listing of this
  75. // directory.
  76. //
  77. *ppTsDirectoryHeader = NULL;
  78. bSuccess = TsCheckOutCachedBlob( tsCache,
  79. pszDirectoryName,
  80. RESERVED_DEMUX_DIRECTORY_LISTING,
  81. ( PVOID * )&pvBlob,
  82. &ulSize );
  83. if ( bSuccess )
  84. {
  85. ASSERT( BLOB_IS_OR_WAS_CACHED( pvBlob ) );
  86. *ppTsDirectoryHeader = (PTS_DIRECTORY_HEADER )pvBlob;
  87. ASSERT ( (*ppTsDirectoryHeader)->IsValid());
  88. //
  89. // Make sure the user tokens match
  90. //
  91. if ( hListingUser == (*ppTsDirectoryHeader)->QueryListingUser() )
  92. {
  93. IF_DEBUG( DIR_LIST) {
  94. DBGPRINTF( (DBG_CONTEXT,
  95. " Obtained DirectoryListing (%s) from Cache ( %08x)\n",
  96. pszDirectoryName,
  97. *ppTsDirectoryHeader));
  98. (*ppTsDirectoryHeader)->Print();
  99. }
  100. return TRUE;
  101. }
  102. //
  103. // User token doesn't match, don't return it
  104. //
  105. bSuccess = TsCheckInCachedBlob( pvBlob );
  106. ASSERT( bSuccess );
  107. }
  108. //
  109. // The block was not present in cache.
  110. // Obtain a fresh copy of the directory listing and cache it.
  111. //
  112. IF_DEBUG( DIR_LIST) {
  113. DBGPRINTF( (DBG_CONTEXT,
  114. "Missing DirListing (%s) in cache. Generating newly\n",
  115. pszDirectoryName));
  116. }
  117. *ppTsDirectoryHeader = TsGetFreshDirectoryHeader(
  118. tsCache,
  119. pszDirectoryName,
  120. hListingUser );
  121. bSuccess = ( *ppTsDirectoryHeader != NULL);
  122. return ( bSuccess);
  123. } // TsGetDirectoryListing
  124. dllexp
  125. BOOL
  126. TsFreeDirectoryListing(
  127. IN const TSVC_CACHE & tsCache,
  128. IN PTS_DIRECTORY_HEADER pDirectoryHeader
  129. )
  130. {
  131. BOOL fReturn;
  132. BOOL fCached = BLOB_IS_OR_WAS_CACHED( (PVOID ) pDirectoryHeader);
  133. IF_DEBUG( DIR_LIST) {
  134. DBGPRINTF( ( DBG_CONTEXT,
  135. "TsFreeDirectoryListing( %08x) called. Cached = %d\n",
  136. pDirectoryHeader,
  137. fCached));
  138. pDirectoryHeader->Print();
  139. }
  140. if ( fCached )
  141. {
  142. fReturn = TsCheckInCachedBlob( ( PVOID )pDirectoryHeader );
  143. }
  144. else
  145. {
  146. fReturn = TsFree( tsCache, ( PVOID )pDirectoryHeader );
  147. }
  148. return( fReturn);
  149. } // TsFreeDirectoryListing()
  150. BOOL
  151. FreeDirectoryHeaderContents(
  152. PVOID pvOldBlock
  153. )
  154. {
  155. PTS_DIRECTORY_HEADER pDirectoryHeader;
  156. pDirectoryHeader = ( PTS_DIRECTORY_HEADER )pvOldBlock;
  157. pDirectoryHeader->CleanupThis();
  158. //
  159. // The item may never have been added to the cache, don't
  160. // count it in this case
  161. //
  162. if ( BLOB_IS_OR_WAS_CACHED( pvOldBlock ) )
  163. {
  164. DEC_COUNTER( BLOB_GET_SVC_ID( pvOldBlock ),
  165. CurrentDirLists );
  166. }
  167. return ( TRUE);
  168. } // FreeDirectoryHeaderContents()
  169. int __cdecl
  170. AlphaCompareFileBothDirInfo(
  171. IN const void * pvFileInfo1,
  172. IN const void * pvFileInfo2)
  173. {
  174. const FILE_BOTH_DIR_INFORMATION * pFileInfo1 =
  175. *((const FILE_BOTH_DIR_INFORMATION **) pvFileInfo1);
  176. const FILE_BOTH_DIR_INFORMATION * pFileInfo2 =
  177. *((const FILE_BOTH_DIR_INFORMATION **) pvFileInfo2);
  178. ASSERT( pFileInfo1 != NULL && pFileInfo2 != NULL);
  179. return ( lstrcmp( (LPCSTR )pFileInfo1->FileName,
  180. (LPCSTR )pFileInfo2->FileName));
  181. } // AlphaCompareFileBothDirInfo()
  182. BOOL
  183. SortInPlaceFileInfoPointers(
  184. IN OUT PFILE_BOTH_DIR_INFORMATION * prgFileInfo,
  185. IN int nEntries,
  186. IN PFN_CMP_FILE_BOTH_DIR_INFO pfnCompare)
  187. /*++
  188. This is a generic function to sort the pointers to file information
  189. array in place using pfnCompare to compare the records for ordering.
  190. Returns:
  191. TRUE on success and FALSE on failure.
  192. --*/
  193. {
  194. DWORD dwTime;
  195. #ifdef INSERTION_SORT
  196. int idxInner;
  197. int idxOuter;
  198. dwTime = GetTickCount();
  199. //
  200. // A simple insertion sort is performed. May be modified in future.
  201. //
  202. for( idxOuter = 1; idxOuter < nEntries; idxOuter++) {
  203. for( idxInner = idxOuter; idxInner > 0; idxInner-- ) {
  204. int iCmp = ( *pfnCompare)( prgFileInfo[ idxInner - 1],
  205. prgFileInfo[ idxInner]);
  206. if ( iCmp <= 0) {
  207. //
  208. // The entries in prgFileInfo[0 .. idxOuter] are in order.
  209. // Stop bubbling the outer down.
  210. //
  211. break;
  212. } else {
  213. //
  214. // Swap the two entries. idxInner, idxInner - 1
  215. //
  216. PFILE_BOTH_DIR_INFORMATION pFInfoTmp;
  217. pFInfoTmp = prgFileInfo[ idxInner - 1];
  218. prgFileInfo[ idxInner - 1] = prgFileInfo[idxInner];
  219. prgFileInfo[ idxInner] = pFInfoTmp;
  220. }
  221. } // inner for
  222. } // for
  223. dwTime = GetTickCount() - dwTime;
  224. # else
  225. IF_DEBUG( DIR_LIST) {
  226. DBGPRINTF( ( DBG_CONTEXT,
  227. "Qsorting the FileInfo Array %08x ( Total = %d)\n",
  228. prgFileInfo, nEntries));
  229. }
  230. dwTime = GetTickCount();
  231. qsort( (PVOID ) prgFileInfo, nEntries,
  232. sizeof( PFILE_BOTH_DIR_INFORMATION),
  233. pfnCompare);
  234. dwTime = GetTickCount() - dwTime;
  235. # endif // INSERTION_SORT
  236. IF_DEBUG( DIR_LIST) {
  237. DBGPRINTF( ( DBG_CONTEXT,
  238. " Time to sort %d entries = %d\n",
  239. nEntries, dwTime));
  240. }
  241. return ( TRUE);
  242. } // SortInPlaceFileInfoPointers()
  243. /**********************************************************************
  244. * TS_DIRECTORY_HEADER related member functions
  245. **********************************************************************/
  246. inline USHORT
  247. ConvertUnicodeToAnsiInPlace(
  248. IN OUT LPWSTR pwszUnicode,
  249. IN USHORT usLen)
  250. /*++
  251. Converts given Unicode strings to Ansi In place and returns the
  252. length of the modified string.
  253. --*/
  254. {
  255. CHAR achAnsi[MAX_PATH+1];
  256. DWORD cch;
  257. if ( usLen > sizeof(achAnsi) )
  258. {
  259. ASSERT( FALSE );
  260. *pwszUnicode = L'\0';
  261. return 0;
  262. }
  263. //
  264. // usLen is a byte count and the unicode string isn't terminated
  265. //
  266. cch = WideCharToMultiByte( CP_ACP,
  267. WC_COMPOSITECHECK,
  268. pwszUnicode,
  269. usLen / sizeof(WCHAR),
  270. achAnsi,
  271. sizeof( achAnsi ),
  272. NULL,
  273. NULL );
  274. if ( !cch || (cch + 1) > sizeof( achAnsi ) )
  275. {
  276. ASSERT( FALSE );
  277. *pwszUnicode = L'\0';
  278. return 0;
  279. }
  280. achAnsi[cch] = '\0';
  281. RtlCopyMemory( pwszUnicode, achAnsi, cch + 1 );
  282. return (USHORT) cch;
  283. } // ConvertUnicodeToAnsiInPlace()
  284. BOOL
  285. TS_DIRECTORY_HEADER::ReadFromNtDirectoryFile(
  286. IN LPCWSTR pwszDirectoryName,
  287. IN OUT DWORD * pcbMemUsed
  288. )
  289. /*++
  290. Opens and reads the directory file for given directory to obtain
  291. information about files and directories in the dir.
  292. Returns:
  293. TRUE on success and FALSE on failure.
  294. Use GetLastError() for further error information.
  295. --*/
  296. {
  297. BOOL fReturn = TRUE; // default assumed.
  298. UNICODE_STRING PathName;
  299. RTL_RELATIVE_NAME RelativeName;
  300. OBJECT_ATTRIBUTES Obja;
  301. HANDLE hFindFile = INVALID_HANDLE_VALUE;
  302. NTSTATUS Status;
  303. IO_STATUS_BLOCK IoStatusBlock;
  304. BOOL fFirstTime;
  305. DWORD cbExtraMem = 0;
  306. PFILE_BOTH_DIR_INFORMATION pFileDirInfo;
  307. PFILE_BOTH_DIR_INFORMATION pFileDirInfoPrior;
  308. //
  309. // Initialize the variables properly
  310. //
  311. memset( (PVOID ) &PathName, 0, sizeof(PathName));
  312. memset( (PVOID ) &RelativeName, 0, sizeof(RelativeName));
  313. //
  314. // Convert the DOS name of directory to NT name for NT API to open it.
  315. //
  316. fReturn = RtlDosPathNameToNtPathName_U(
  317. pwszDirectoryName,
  318. &PathName,
  319. NULL,
  320. &RelativeName );
  321. //
  322. // If translation fails or
  323. // If this directory name is in the form <dirname>\<filespec>,
  324. // the caller has messed up.
  325. //
  326. if ( !fReturn || RelativeName.RelativeName.Length != 0) {
  327. SetLastError(ERROR_PATH_NOT_FOUND);
  328. if ( PathName.Buffer != NULL) { // free up the space.
  329. RtlFreeHeap( RtlProcessHeap(), 0, PathName.Buffer);
  330. }
  331. return( fReturn);
  332. }
  333. //
  334. // Remember that we need to free the buffer containing the NT name.
  335. // i.e. PathName.Buffer
  336. //
  337. ASSERT( RelativeName.ContainingDirectory == NULL );
  338. ASSERT( RelativeName.RelativeName.Length == 0);
  339. InitializeObjectAttributes( &Obja,
  340. &PathName,
  341. OBJ_CASE_INSENSITIVE,
  342. RelativeName.ContainingDirectory,
  343. NULL );
  344. //
  345. // Open the directory for list access
  346. //
  347. Status = NtOpenFile( &hFindFile,
  348. FILE_LIST_DIRECTORY | SYNCHRONIZE,
  349. &Obja,
  350. &IoStatusBlock,
  351. FILE_SHARE_READ | FILE_SHARE_WRITE,
  352. FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT |
  353. FILE_OPEN_FOR_BACKUP_INTENT
  354. );
  355. //
  356. // The Buffer is not required any more. Free it before checking status.
  357. //
  358. RtlFreeHeap( RtlProcessHeap(), 0, PathName.Buffer );
  359. PathName.Buffer = NULL;
  360. if ( !NT_SUCCESS(Status) ) {
  361. //
  362. // The full path does not refer to a directory. This could be a
  363. // device, but (unlike FindFirstFile()...) we don't care. If it's
  364. // not a directory, we don't list it.
  365. //
  366. IF_DEBUG( DIR_LIST) {
  367. DBGPRINTF( ( DBG_CONTEXT, "Failed to open Dir %ws. Handle = %x\n",
  368. pwszDirectoryName, hFindFile));
  369. }
  370. SetLastError( ERROR_PATH_NOT_FOUND);
  371. return ( FALSE);
  372. }
  373. InitializeListHead( &m_listDirectoryBuffers);
  374. //
  375. // Loop through getting subsequent entries in the directory.
  376. //
  377. for( fFirstTime = TRUE; ; fFirstTime = FALSE)
  378. {
  379. PVOID pvBuffer;
  380. //
  381. // Get the next chunk of directory information.
  382. // Obtained in a buffer with LIST_ENTRY as the first member of buffer
  383. //
  384. #define DIR_ALLOC_SIZE (DIRECTORY_BUFFER_SIZE + sizeof (LIST_ENTRY))
  385. pvBuffer = ALLOC( DIR_ALLOC_SIZE );
  386. cbExtraMem += DIR_ALLOC_SIZE;
  387. if ( pvBuffer == NULL ) {
  388. //
  389. // Allocation failure.
  390. //
  391. SetLastError( ERROR_NOT_ENOUGH_MEMORY);
  392. fReturn = FALSE;
  393. break; // Get out of the loop with failure.
  394. }
  395. pFileDirInfo = ( PFILE_BOTH_DIR_INFORMATION )
  396. ((( PCHAR ) pvBuffer) + sizeof( LIST_ENTRY ) );
  397. Status = NtQueryDirectoryFile( hFindFile, // fileHandle
  398. NULL, // Event
  399. NULL, // Apc Routine
  400. NULL, // ApcContext
  401. &IoStatusBlock, // PIoStatusBlock
  402. pFileDirInfo, // PFileInfo
  403. DIRECTORY_BUFFER_SIZE, // Len
  404. FileBothDirectoryInformation, //Class
  405. FALSE, // fSingleEntry ?
  406. NULL, //FileName
  407. fFirstTime ); // RestartScan ?
  408. //
  409. // If the NT API returns STATUS_NO_MORE_FILES, then it did not use
  410. // our buffer at all. We can just free it.
  411. // Now's the time that we leave this loop, and stop reading the
  412. // directory file.
  413. //
  414. if ( Status == STATUS_NO_MORE_FILES ) {
  415. FREE( pvBuffer );
  416. //
  417. // Decrement the memory size so we don't get charged for it
  418. //
  419. cbExtraMem -= DIR_ALLOC_SIZE;
  420. fReturn = TRUE;
  421. break;
  422. }
  423. if ( NT_SUCCESS( Status ) || ( Status == STATUS_BUFFER_OVERFLOW ) )
  424. {
  425. ULONG Offset;
  426. //
  427. // The buffer contains directory entries.
  428. // Place it on the list of such buffers for this directory.
  429. //
  430. InsertBufferInTail( (PLIST_ENTRY ) pvBuffer);
  431. pFileDirInfoPrior = NULL;
  432. //
  433. // Scan thru the entries in the buffer,
  434. // truncate the last entry ( if partial) and
  435. // convert the Unicode strings, inplace to ansi strings.
  436. //
  437. do
  438. {
  439. pFileDirInfoPrior = pFileDirInfo;
  440. Offset = pFileDirInfo->NextEntryOffset;
  441. if ( ( Offset == 0 ) && ( Status == STATUS_BUFFER_OVERFLOW ) )
  442. {
  443. //
  444. // If Status==STATUS_BUFFER_OVERFLOW, the last entry in
  445. // buffer may be a partial entry, broken on boundary of
  446. // buffer. The NT API will give us this entry again next
  447. // time around, so for now we patch the buffer up to
  448. // appear as if it does not contain the partial entry.
  449. //
  450. if ( pFileDirInfoPrior != NULL )
  451. {
  452. pFileDirInfoPrior->NextEntryOffset = 0;
  453. }
  454. else
  455. {
  456. //
  457. // Some fatal problem. Should get out this loop.
  458. //
  459. BREAKPOINT();
  460. fReturn = FALSE;
  461. goto Failure;
  462. }
  463. }
  464. else
  465. {
  466. IncrementDirEntries();
  467. if ( pFileDirInfo->FileNameLength != 0) {
  468. pFileDirInfo->FileNameLength =
  469. ConvertUnicodeToAnsiInPlace(
  470. (LPWSTR ) pFileDirInfo->FileName,
  471. (USHORT ) pFileDirInfo->FileNameLength);
  472. }
  473. if ( pFileDirInfo->ShortNameLength != 0) {
  474. pFileDirInfo->ShortNameLength =
  475. (CCHAR ) ConvertUnicodeToAnsiInPlace(
  476. (LPWSTR ) pFileDirInfo->ShortName,
  477. pFileDirInfo->ShortNameLength);
  478. }
  479. // Get the next entry in buffer
  480. pFileDirInfo =
  481. ( PFILE_BOTH_DIR_INFORMATION )
  482. ((( PCHAR )pFileDirInfo ) + Offset );
  483. }
  484. }
  485. while ( Offset != 0);
  486. Status = STATUS_SUCCESS;
  487. }
  488. if ( !NT_SUCCESS(Status) )
  489. {
  490. fReturn = FALSE;
  491. break;
  492. }
  493. }
  494. Failure:
  495. ASSERT( PathName.Buffer == NULL);
  496. if ( hFindFile != INVALID_HANDLE_VALUE) {
  497. NtClose( hFindFile );
  498. hFindFile = INVALID_HANDLE_VALUE;
  499. }
  500. *pcbMemUsed += cbExtraMem;
  501. return ( fReturn);
  502. } // TS_DIRECTORY_HEADER::ReadFromNtDirectoryFile()
  503. VOID
  504. TS_DIRECTORY_HEADER::CleanupThis( VOID)
  505. {
  506. PLIST_ENTRY pEntry;
  507. PLIST_ENTRY pNextEntry;
  508. for ( pEntry = QueryDirBuffersListEntry()->Flink;
  509. pEntry != QueryDirBuffersListEntry();
  510. pEntry = pNextEntry )
  511. {
  512. pNextEntry = pEntry->Flink;
  513. //
  514. // The buffers are allocated such that first member of buffer is
  515. // LIST_ENTRY object. Free it entirely.
  516. //
  517. FREE( pEntry );
  518. }
  519. InitializeListHead( QueryDirBuffersListEntry());
  520. if ( m_ppFileInfo != NULL) {
  521. FREE( m_ppFileInfo);
  522. m_ppFileInfo = NULL;
  523. }
  524. m_hListingUser = INVALID_HANDLE_VALUE;
  525. m_nEntries = 0;
  526. return;
  527. } // TS_DIRECTORY_HEADER::CleanupThis()
  528. BOOL
  529. TS_DIRECTORY_HEADER::BuildFileInfoPointers(
  530. IN OUT DWORD * pcbMemUsed
  531. )
  532. /*++
  533. This constructs the indirection pointers from the buffers containing the
  534. file information.
  535. This array of indirection enables faster access to the file information
  536. structures stored.
  537. Should be always called after ReadFromNtDirectoryFile() to construct the
  538. appropriate pointers.
  539. Returns:
  540. TRUE on success and FALSE if there are any failures.
  541. --*/
  542. {
  543. BOOL fReturn = FALSE;
  544. DWORD cbAlloc;
  545. ASSERT( QueryNumEntries() != 0); // Any directory will atleast have "."
  546. //
  547. // Alloc space for holding the pointers for numEntries pointers.
  548. //
  549. cbAlloc = QueryNumEntries() * sizeof( PFILE_BOTH_DIR_INFORMATION );
  550. m_ppFileInfo = (PFILE_BOTH_DIR_INFORMATION *) ALLOC( cbAlloc );
  551. if ( m_ppFileInfo != NULL ) {
  552. int index;
  553. PLIST_ENTRY pEntry;
  554. ULONG Offset;
  555. PFILE_BOTH_DIR_INFORMATION pFileDirInfo;
  556. //
  557. // Get the link to first buffer and start enumeration.
  558. //
  559. pEntry = QueryDirBuffersListEntry()->Flink;
  560. pFileDirInfo = (PFILE_BOTH_DIR_INFORMATION )( pEntry + 1 );
  561. for ( index = 0;
  562. index < QueryNumEntries();
  563. index++ ) {
  564. ASSERT( pEntry != QueryDirBuffersListEntry());
  565. m_ppFileInfo[index] = pFileDirInfo; // store the pointer.
  566. Offset = pFileDirInfo->NextEntryOffset;
  567. if ( Offset != 0 ) {
  568. pFileDirInfo = (PFILE_BOTH_DIR_INFORMATION )
  569. ((( PCHAR )pFileDirInfo ) + Offset );
  570. } else {
  571. //
  572. // we are moving to the next buffer.
  573. //
  574. pEntry = pEntry->Flink;
  575. if ( pEntry == QueryDirBuffersListEntry()) {
  576. ASSERT( index == QueryNumEntries() - 1);
  577. break;
  578. }
  579. pFileDirInfo = ( PFILE_BOTH_DIR_INFORMATION )( pEntry + 1 );
  580. }
  581. } // for
  582. ASSERT( Offset == 0 );
  583. fReturn = SortInPlaceFileInfoPointers( m_ppFileInfo,
  584. QueryNumEntries(),
  585. AlphaCompareFileBothDirInfo);
  586. } // valid alloc of the pointers.
  587. *pcbMemUsed += cbAlloc;
  588. return ( fReturn);
  589. } // TS_DIRECTORY_HEADER::BuildFileInfoPointers()
  590. # if DBG
  591. VOID
  592. TS_DIRECTORY_HEADER::Print( VOID) const
  593. {
  594. DBGPRINTF( ( DBG_CONTEXT,
  595. "Printing TS_DIRECTORY_HEADER ( %08x).\n", this));
  596. DBGPRINTF( ( DBG_CONTEXT,
  597. "ListingUser Handle = %08x\t Num Entries = %08x\n",
  598. m_hListingUser, m_nEntries));
  599. DBGPRINTF( ( DBG_CONTEXT,
  600. "Pointer to array of indirection pointers %08x\n",
  601. m_ppFileInfo));
  602. //
  603. // The buffers containing the data of the file information not printed
  604. //
  605. return;
  606. } // TS_DIRECTORY_HEADER::Print()
  607. # endif // DBG
  608. /************************ End of File ***********************/
  609.