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.

1687 lines
41 KiB

  1. /*++
  2. Copyright (c) 1995 Microsoft Corporation
  3. Module Name:
  4. Tunnel.c
  5. Abstract:
  6. The tunnel package provides a set of routines that allow compatibility
  7. with applications that rely on filesystems being able to "hold onto"
  8. file meta-info for a short period of time after deletion/renaming and
  9. reinstantiating a new directory entry with that meta-info if a
  10. create/rename occurs to cause a file of that name to appear again in a
  11. short period of time.
  12. Note that this violates POSIX rules. This package should not be used
  13. on POSIX fileobjects, i.e. fileobjects that have case-sensitive names.
  14. Entries are keyed by directory and one of the short/long names. An opaque
  15. rock of information is also associated (create time, last write time, etc.).
  16. This is expected to vary on a per-filesystem basis.
  17. A TUNNEL variable should be initialized for every volume in the system
  18. at mount time. Thereafter, each delete/rename-out should add to the tunnel
  19. and each create/rename-in should read from the tunnel. Each directory
  20. deletion should also notify the package so that all associated entries can
  21. be flushed. The package is responsible for cleaning out aged entries.
  22. Tunneled information is in the paged pool.
  23. Concurrent access to the TUNNEL variable is controlled by this package.
  24. Callers are responsible for synchronizing access to the FsRtlDeleteTunnelCache
  25. call.
  26. The functions provided in this package are as follows:
  27. o FsRtlInitializeTunnel - Initializes the TUNNEL package (called once per boot)
  28. o FsRtlInitializeTunnelCache - Initializes a TUNNEL structure (called once on mount)
  29. o FsRtlAddToTunnelCache - Adds a new key/value pair to the tunnel
  30. o FsRtlFindInTunnelCache - Finds and returns a key/value from the tunnel
  31. o FsRtlDeleteKeyFromTunnelCache - Deletes all entries with a given
  32. directory key from the tunnel
  33. o FsRtlDeleteTunnelCache - Deletes a TUNNEL structure
  34. Author:
  35. Dan Lovinger [DanLo] 8-Aug-1995
  36. Revision History:
  37. --*/
  38. #include "FsRtlP.h"
  39. #ifndef INLINE
  40. #define INLINE __inline
  41. #endif
  42. //
  43. // Registry keys/values for controlling tunneling
  44. //
  45. #define TUNNEL_KEY_NAME L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\FileSystem"
  46. #define TUNNEL_AGE_VALUE_NAME L"MaximumTunnelEntryAgeInSeconds"
  47. #define TUNNEL_SIZE_VALUE_NAME L"MaximumTunnelEntries"
  48. #define KEY_WORK_AREA ((sizeof(KEY_VALUE_FULL_INFORMATION) + sizeof(ULONG)) + 64)
  49. //
  50. // Tunnel expiration paramters (cached once at startup)
  51. //
  52. #ifdef ALLOC_DATA_PRAGMA
  53. #pragma data_seg("PAGEDATA")
  54. #endif
  55. ULONG TunnelMaxEntries = 256; // Value for !MmIsThisAnNtAsSystem()
  56. ULONG TunnelMaxAge = 15;
  57. #ifdef ALLOC_DATA_PRAGMA
  58. #pragma data_seg()
  59. #endif
  60. //
  61. // We use a lookaside list to manage the common size tunnel entry. The common size
  62. // is contrived to be 128 bytes by adjusting the size we defer for the long name
  63. // to 16 characters, which is pretty reasonable. If we ever expect to get more than
  64. // a ULONGLONG data element or common names are observed to become larger, adjusting
  65. // this may be required.
  66. //
  67. PAGED_LOOKASIDE_LIST TunnelLookasideList;
  68. #define MAX_LOOKASIDE_DEPTH 256
  69. #define LOOKASIDE_NODE_SIZE ( sizeof(TUNNEL_NODE) + \
  70. sizeof(WCHAR)*(8+1+3) + \
  71. sizeof(WCHAR)*(16) + \
  72. sizeof(ULONGLONG) )
  73. //
  74. // Flag bits in the TUNNEL_NODE
  75. //
  76. #define TUNNEL_FLAG_NON_LOOKASIDE 0x1
  77. #define TUNNEL_FLAG_KEY_SHORT 0x2
  78. //
  79. // A node of tunneled information in the cache
  80. //
  81. // A TUNNEL is allocated in each VCB and initialized at mount time.
  82. //
  83. // TUNNEL_NODES are then arranged off of the TUNNEL in a splay tree keyed
  84. // by DirKey ## Name, where Name is whichever of the names was removed from
  85. // the directory (short or long). Each node is also timestamped and inserted
  86. // into a timer queue for age expiration.
  87. //
  88. typedef struct {
  89. //
  90. // Splay links in the Cache tree
  91. //
  92. RTL_SPLAY_LINKS CacheLinks;
  93. //
  94. // List links in the timer queue
  95. //
  96. LIST_ENTRY ListLinks;
  97. //
  98. // Time this entry was created (for constant time insert)
  99. //
  100. LARGE_INTEGER CreateTime;
  101. //
  102. // Directory these names are associated with
  103. //
  104. ULONGLONG DirKey;
  105. //
  106. // Flags for the entry
  107. //
  108. ULONG Flags;
  109. //
  110. // Long/Short names of the file
  111. //
  112. UNICODE_STRING LongName;
  113. UNICODE_STRING ShortName;
  114. //
  115. // Opaque tunneled data
  116. //
  117. PVOID TunnelData;
  118. ULONG TunnelDataLength;
  119. } TUNNEL_NODE, *PTUNNEL_NODE;
  120. //
  121. // Internal utility functions
  122. //
  123. NTSTATUS
  124. FsRtlGetTunnelParameterValue (
  125. IN PUNICODE_STRING ValueName,
  126. IN OUT PULONG Value);
  127. VOID
  128. FsRtlPruneTunnelCache (
  129. IN PTUNNEL Cache,
  130. IN OUT PLIST_ENTRY FreePoolList);
  131. #ifdef ALLOC_PRAGMA
  132. #pragma alloc_text(INIT, FsRtlInitializeTunnels)
  133. #pragma alloc_text(PAGE, FsRtlInitializeTunnelCache)
  134. #pragma alloc_text(PAGE, FsRtlAddToTunnelCache)
  135. #pragma alloc_text(PAGE, FsRtlFindInTunnelCache)
  136. #pragma alloc_text(PAGE, FsRtlDeleteKeyFromTunnelCache)
  137. #pragma alloc_text(PAGE, FsRtlDeleteTunnelCache)
  138. #pragma alloc_text(PAGE, FsRtlPruneTunnelCache)
  139. #pragma alloc_text(PAGE, FsRtlGetTunnelParameterValue)
  140. #endif
  141. //
  142. // Testing and usermode rig support. Define TUNNELTEST to get verbose debugger
  143. // output on various operations. Define USERTEST to transform the code into
  144. // a form which can be compiled in usermode for more efficient debugging.
  145. //
  146. #if defined(TUNNELTEST) || defined(KEYVIEW)
  147. VOID DumpUnicodeString(UNICODE_STRING *s);
  148. VOID DumpNode( TUNNEL_NODE *Node, ULONG Indent );
  149. VOID DumpTunnel( TUNNEL *Tunnel );
  150. #define DblHex64(a) (ULONG)((a >> 32) & 0xffffffff),(ULONG)(a & 0xffffffff)
  151. #endif // TUNNELTEST
  152. #ifdef USERTEST
  153. #include <stdio.h>
  154. #undef KeQuerySystemTime
  155. #define KeQuerySystemTime NtQuerySystemTime
  156. #undef ExInitializeFastMutex
  157. #define ExInitializeFastMutex(arg)
  158. #define ExAcquireFastMutex(arg)
  159. #define ExReleaseFastMutex(arg)
  160. #define DbgPrint printf
  161. #undef PAGED_CODE
  162. #define PAGED_CODE()
  163. #endif
  164. INLINE
  165. LONG
  166. FsRtlCompareNodeAndKey (
  167. TUNNEL_NODE *Node,
  168. ULONGLONG DirectoryKey,
  169. PUNICODE_STRING Name
  170. )
  171. /*++
  172. Routine Description:
  173. Compare a tunnel node with a key/name pair
  174. Arguments:
  175. Node - a tunnel node
  176. DirectoryKey - a key value
  177. Name - a filename
  178. Return Value:
  179. Signed comparison result
  180. --*/
  181. {
  182. return (Node->DirKey > DirectoryKey ? 1 :
  183. (Node->DirKey < DirectoryKey ? -1 :
  184. RtlCompareUnicodeString((FlagOn(Node->Flags, TUNNEL_FLAG_KEY_SHORT) ?
  185. &Node->ShortName : &Node->LongName),
  186. Name,
  187. TRUE)));
  188. }
  189. INLINE
  190. VOID
  191. FsRtlFreeTunnelNode (
  192. PTUNNEL_NODE Node,
  193. PLIST_ENTRY FreePoolList OPTIONAL
  194. )
  195. /*++
  196. Routine Description:
  197. Free a node
  198. Arguments:
  199. Node - a tunnel node to free
  200. FreePoolList - optional list to hold freeable pool memory
  201. Return Value:
  202. None
  203. -*/
  204. {
  205. if (FreePoolList) {
  206. InsertHeadList(FreePoolList, &Node->ListLinks);
  207. } else {
  208. if (FlagOn(Node->Flags, TUNNEL_FLAG_NON_LOOKASIDE)) {
  209. ExFreePool(Node);
  210. } else {
  211. ExFreeToPagedLookasideList(&TunnelLookasideList, Node);
  212. }
  213. }
  214. }
  215. INLINE
  216. VOID
  217. FsRtlEmptyFreePoolList (
  218. PLIST_ENTRY FreePoolList
  219. )
  220. /*++
  221. Routine Description:
  222. Free all pool memory that has been delayed onto a free list.
  223. Arguments:
  224. FreePoolList - a list of freeable pool memory
  225. Return Value:
  226. None
  227. -*/
  228. {
  229. PTUNNEL_NODE FreeNode;
  230. while (!IsListEmpty(FreePoolList)) {
  231. FreeNode = CONTAINING_RECORD(FreePoolList->Flink, TUNNEL_NODE, ListLinks);
  232. RemoveEntryList(FreePoolList->Flink);
  233. FsRtlFreeTunnelNode(FreeNode, NULL);
  234. }
  235. }
  236. INLINE
  237. VOID
  238. FsRtlRemoveNodeFromTunnel (
  239. IN PTUNNEL Cache,
  240. IN PTUNNEL_NODE Node,
  241. IN PLIST_ENTRY FreePoolList,
  242. IN PBOOLEAN Splay OPTIONAL
  243. )
  244. /*++
  245. Routine Description:
  246. Performs the common work of deleting a node from a tunnel cache. Pool memory
  247. is not deleted immediately but is saved aside on a list for deletion later
  248. by the calling routine.
  249. Arguments:
  250. Cache - the tunnel cache the node is in
  251. Node - the node being removed
  252. FreePoolList - an initialized list to take the node if it was allocated from
  253. pool
  254. Splay - an optional flag to indicate whether the tree should be splayed on
  255. the delete. Set to FALSE if splaying was performed.
  256. Return Value:
  257. None.
  258. --*/
  259. {
  260. if (Splay && *Splay) {
  261. Cache->Cache = RtlDelete(&Node->CacheLinks);
  262. *Splay = FALSE;
  263. } else {
  264. RtlDeleteNoSplay(&Node->CacheLinks, &Cache->Cache);
  265. }
  266. RemoveEntryList(&Node->ListLinks);
  267. Cache->NumEntries--;
  268. FsRtlFreeTunnelNode(Node, FreePoolList);
  269. }
  270. VOID
  271. FsRtlInitializeTunnels (
  272. VOID
  273. )
  274. /*++
  275. Routine Description:
  276. Initializes the global part of the tunneling package.
  277. Arguments:
  278. None
  279. Return Value:
  280. None
  281. --*/
  282. {
  283. UNICODE_STRING ValueName;
  284. USHORT LookasideDepth;
  285. PAGED_CODE();
  286. if (MmIsThisAnNtAsSystem()) {
  287. TunnelMaxEntries = 1024;
  288. }
  289. //
  290. // Query our configurable parameters
  291. //
  292. // Don't worry about failure in retrieving from the registry. We've gotten
  293. // this far so fall back on defaults even if there was a problem with resources.
  294. //
  295. ValueName.Buffer = TUNNEL_SIZE_VALUE_NAME;
  296. ValueName.Length = sizeof(TUNNEL_SIZE_VALUE_NAME) - sizeof(WCHAR);
  297. ValueName.MaximumLength = sizeof(TUNNEL_SIZE_VALUE_NAME);
  298. (VOID) FsRtlGetTunnelParameterValue(&ValueName, &TunnelMaxEntries);
  299. ValueName.Buffer = TUNNEL_AGE_VALUE_NAME;
  300. ValueName.Length = sizeof(TUNNEL_AGE_VALUE_NAME) - sizeof(WCHAR);
  301. ValueName.MaximumLength = sizeof(TUNNEL_AGE_VALUE_NAME);
  302. (VOID) FsRtlGetTunnelParameterValue(&ValueName, &TunnelMaxAge);
  303. if (TunnelMaxAge == 0) {
  304. //
  305. // If the registry has been set so the timeout is zero, we should force
  306. // the number of entries to zero also. This preserves expectations and lets
  307. // us key off of max entries alone in performing the hard disabling of the
  308. // caching code.
  309. //
  310. TunnelMaxEntries = 0;
  311. }
  312. //
  313. // Convert from seconds to 10ths of msecs, the internal resolution
  314. //
  315. TunnelMaxAge *= 10000000;
  316. //
  317. // Build the lookaside list for common node allocation
  318. //
  319. if (TunnelMaxEntries > MAXUSHORT) {
  320. //
  321. // User is hinting a big need to us
  322. //
  323. LookasideDepth = MAX_LOOKASIDE_DEPTH;
  324. } else {
  325. LookasideDepth = ((USHORT)TunnelMaxEntries)/16;
  326. }
  327. if (LookasideDepth == 0 && TunnelMaxEntries) {
  328. //
  329. // Miniscule number of entries allowed. Lookaside 'em all.
  330. //
  331. LookasideDepth = (USHORT)TunnelMaxEntries + 1;
  332. }
  333. if (LookasideDepth > MAX_LOOKASIDE_DEPTH) {
  334. //
  335. // Finally, restrict the depth to something reasonable.
  336. //
  337. LookasideDepth = MAX_LOOKASIDE_DEPTH;
  338. }
  339. ExInitializePagedLookasideList( &TunnelLookasideList,
  340. NULL,
  341. NULL,
  342. 0,
  343. LOOKASIDE_NODE_SIZE,
  344. 'LnuT',
  345. LookasideDepth );
  346. return;
  347. }
  348. //
  349. // *** SPEC
  350. //
  351. // FsRtlInitializeTunnelCache - Initialize a tunneling cache for a volume
  352. //
  353. // FsRtlInitializeTunnelCache will allocate a default cache (resizing policy is common
  354. // to all file systems) and initialize it to be empty. File systems will store a pointer to
  355. // this cache in their per-volume structures.
  356. //
  357. // Information is retained in the tunnel cache for a fixed period of time. MarkZ would
  358. // assume that a value of 10 seconds would satisfy the vast majority of situations. This
  359. // could be controlled by the registry or could be a compilation constant.
  360. //
  361. // Change: W95 times out at 15 seconds. Would be a registry value initialized at tunnel
  362. // creation time, with a proposed default of 15 seconds.
  363. //
  364. VOID
  365. FsRtlInitializeTunnelCache (
  366. IN PTUNNEL Cache
  367. )
  368. /*++
  369. Routine Description:
  370. Initialize a new tunnel cache.
  371. Arguments:
  372. None
  373. Return Value:
  374. None
  375. --*/
  376. {
  377. PAGED_CODE();
  378. ExInitializeFastMutex(&Cache->Mutex);
  379. Cache->Cache = NULL;
  380. InitializeListHead(&Cache->TimerQueue);
  381. Cache->NumEntries = 0;
  382. return;
  383. }
  384. //
  385. // *** SPEC
  386. //
  387. // FsRtlAddToTunnelCache - add information to a tunnel cache
  388. //
  389. // FsRtlAddToTunnelCache is called by file systems when a name disappears from a
  390. // directory. This typically occurs in both the delete and the rename paths. When
  391. // a name is deleted, all information needed to be cached is extracted from the file
  392. // and passed in a single buffer. This information is stored keyed by the directory key
  393. // (a ULONG that is unique to the directory) and the short-name of the file.
  394. //
  395. // The caller is required to synchronize this call against FsRtlDeleteTunnelCache.
  396. //
  397. // Arguments:
  398. // Cache pointer to cache initialized by FsRtlInitializeTunnelCache
  399. // DirectoryKey ULONG unique ID of the directory containing the deleted file
  400. // ShortName UNICODE_STRING* short (8.3) name of the file
  401. // LongName UNICODE_STRING* full name of the file
  402. // DataLength ULONG length of data to be cached with these names
  403. // Data VOID* data that will be cached.
  404. //
  405. // It is acceptable for the Cache to ignore this request based upon memory constraints.
  406. //
  407. // Change: W95 maintains 10 items in the tunnel cache. Since we are a potential server
  408. // this should be much higher. The max count would be initialized from the registry with
  409. // a proposed default of 1024. Adds which run into the limit would cause least recently
  410. // inserted recycling (i.e., off of the top of the timer queue).
  411. //
  412. // Change: Key should be by the name removed, not neccesarily the short name. If a long name
  413. // is removed, it would be incorrect to miss the tunnel. Use KeyByShortName boolean to specify
  414. // which.
  415. //
  416. // Change: Specify that Data, ShortName, and LongName are copied for storage.
  417. //
  418. VOID
  419. FsRtlAddToTunnelCache (
  420. IN PTUNNEL Cache,
  421. IN ULONGLONG DirKey,
  422. IN PUNICODE_STRING ShortName,
  423. IN PUNICODE_STRING LongName,
  424. IN BOOLEAN KeyByShortName,
  425. IN ULONG DataLength,
  426. IN PVOID Data
  427. )
  428. /*++
  429. Routine Description:
  430. Adds an entry to the tunnel cache keyed by
  431. DirectoryKey ## (KeyByShortName ? ShortName : LongName)
  432. ShortName, LongName, and Data are copied and stored in the tunnel. As a side
  433. effect, if there are too many entries in the tunnel cache, this routine will
  434. initiate expiration in the tunnel cache.
  435. Arguments:
  436. Cache - a tunnel cache initialized by FsRtlInitializeTunnelCache()
  437. DirKey - the key value of the directory the name appeared in
  438. ShortName - (optional if !KeyByShortName) the 8.3 name of the file
  439. LongName - (optional if KeyByShortName) the long name of the file
  440. KeyByShortName - specifies which name is keyed in the tunnel cache
  441. DataLength - specifies the length of the opaque data segment (file
  442. system specific) which contains the tunnelling information for this
  443. file
  444. Data - pointer to the opaque tunneling data segment
  445. Return Value:
  446. None
  447. --*/
  448. {
  449. LONG Compare;
  450. ULONG NodeSize;
  451. PUNICODE_STRING NameKey;
  452. PRTL_SPLAY_LINKS *Links;
  453. LIST_ENTRY FreePoolList;
  454. PTUNNEL_NODE Node = NULL;
  455. PTUNNEL_NODE NewNode = NULL;
  456. BOOLEAN FreeOldNode = FALSE;
  457. BOOLEAN AllocatedFromPool = FALSE;
  458. PAGED_CODE();
  459. //
  460. // If MaxEntries is 0 then tunneling is disabled.
  461. //
  462. if (TunnelMaxEntries == 0) return;
  463. InitializeListHead(&FreePoolList);
  464. //
  465. // Grab a new node for this data
  466. //
  467. NodeSize = sizeof(TUNNEL_NODE) + ShortName->Length + LongName->Length + DataLength;
  468. if (LOOKASIDE_NODE_SIZE >= NodeSize) {
  469. NewNode = ExAllocateFromPagedLookasideList(&TunnelLookasideList);
  470. }
  471. if (NewNode == NULL) {
  472. //
  473. // Data doesn't fit in lookaside nodes
  474. //
  475. NewNode = ExAllocatePoolWithTag(PagedPool|POOL_COLD_ALLOCATION, NodeSize, 'PnuT');
  476. if (NewNode == NULL) {
  477. //
  478. // Give up tunneling this entry
  479. //
  480. return;
  481. }
  482. AllocatedFromPool = TRUE;
  483. }
  484. //
  485. // Traverse the cache to find our insertion point
  486. //
  487. NameKey = (KeyByShortName ? ShortName : LongName);
  488. ExAcquireFastMutex(&Cache->Mutex);
  489. Links = &Cache->Cache;
  490. while (*Links) {
  491. Node = CONTAINING_RECORD(*Links, TUNNEL_NODE, CacheLinks);
  492. Compare = FsRtlCompareNodeAndKey(Node, DirKey, NameKey);
  493. if (Compare > 0) {
  494. Links = &RtlLeftChild(&Node->CacheLinks);
  495. } else {
  496. if (Compare < 0) {
  497. Links = &RtlRightChild(&Node->CacheLinks);
  498. } else {
  499. break;
  500. }
  501. }
  502. }
  503. //
  504. // Thread new data into the splay tree
  505. //
  506. RtlInitializeSplayLinks(&NewNode->CacheLinks);
  507. if (Node) {
  508. //
  509. // Not inserting first node in tree
  510. //
  511. if (*Links) {
  512. //
  513. // Entry exists in the cache, so replace by swapping all splay links
  514. //
  515. RtlRightChild(&NewNode->CacheLinks) = RtlRightChild(*Links);
  516. RtlLeftChild(&NewNode->CacheLinks) = RtlLeftChild(*Links);
  517. if (RtlRightChild(*Links)) RtlParent(RtlRightChild(*Links)) = &NewNode->CacheLinks;
  518. if (RtlLeftChild(*Links)) RtlParent(RtlLeftChild(*Links)) = &NewNode->CacheLinks;
  519. if (!RtlIsRoot(*Links)) {
  520. //
  521. // Change over the parent links. Note that we've messed with *Links now
  522. // since it is pointing at the parent member.
  523. //
  524. RtlParent(&NewNode->CacheLinks) = RtlParent(*Links);
  525. if (RtlIsLeftChild(*Links)) {
  526. RtlLeftChild(RtlParent(*Links)) = &NewNode->CacheLinks;
  527. } else {
  528. RtlRightChild(RtlParent(*Links)) = &NewNode->CacheLinks;
  529. }
  530. } else {
  531. //
  532. // Set root of the cache
  533. //
  534. Cache->Cache = &NewNode->CacheLinks;
  535. }
  536. //
  537. // Free old node
  538. //
  539. RemoveEntryList(&Node->ListLinks);
  540. FsRtlFreeTunnelNode(Node, &FreePoolList);
  541. Cache->NumEntries--;
  542. } else {
  543. //
  544. // Simple insertion as a leaf
  545. //
  546. NewNode->CacheLinks.Parent = &Node->CacheLinks;
  547. *Links = &NewNode->CacheLinks;
  548. }
  549. } else {
  550. Cache->Cache = &NewNode->CacheLinks;
  551. }
  552. //
  553. // Thread onto the timer list
  554. //
  555. KeQuerySystemTime(&NewNode->CreateTime);
  556. InsertTailList(&Cache->TimerQueue, &NewNode->ListLinks);
  557. Cache->NumEntries++;
  558. //
  559. // Stash tunneling information
  560. //
  561. NewNode->DirKey = DirKey;
  562. if (KeyByShortName) {
  563. NewNode->Flags = TUNNEL_FLAG_KEY_SHORT;
  564. } else {
  565. NewNode->Flags = 0;
  566. }
  567. //
  568. // Initialize the internal UNICODE_STRINGS to point at the buffer segments. For various
  569. // reasons (UNICODE APIs are incomplete, we're avoiding calling any allocate routine more
  570. // than once, UNICODE strings are not guaranteed to be null terminated) we have to do a lot
  571. // of this by hand.
  572. //
  573. // The data is layed out like this in the allocated block:
  574. //
  575. // -----------------------------------------------------------------------------------
  576. // | TUNNEL_NODE | Node->ShortName.Buffer | Node->LongName.Buffer | Node->TunnelData |
  577. // -----------------------------------------------------------------------------------
  578. //
  579. NewNode->ShortName.Buffer = (PWCHAR)((PCHAR)NewNode + sizeof(TUNNEL_NODE));
  580. NewNode->LongName.Buffer = (PWCHAR)((PCHAR)NewNode + sizeof(TUNNEL_NODE) + ShortName->Length);
  581. NewNode->ShortName.Length = NewNode->ShortName.MaximumLength = ShortName->Length;
  582. NewNode->LongName.Length = NewNode->LongName.MaximumLength = LongName->Length;
  583. if (ShortName->Length) {
  584. RtlCopyMemory(NewNode->ShortName.Buffer, ShortName->Buffer, ShortName->Length);
  585. }
  586. if (LongName->Length) {
  587. RtlCopyMemory(NewNode->LongName.Buffer, LongName->Buffer, LongName->Length);
  588. }
  589. NewNode->TunnelData = (PVOID)((PCHAR)NewNode + sizeof(TUNNEL_NODE) + ShortName->Length + LongName->Length);
  590. NewNode->TunnelDataLength = DataLength;
  591. RtlCopyMemory(NewNode->TunnelData, Data, DataLength);
  592. if (AllocatedFromPool) {
  593. SetFlag(NewNode->Flags, TUNNEL_FLAG_NON_LOOKASIDE);
  594. }
  595. #if defined(TUNNELTEST) || defined (KEYVIEW)
  596. DbgPrint("FsRtlAddToTunnelCache:\n");
  597. DumpNode(NewNode, 1);
  598. #ifndef KEYVIEW
  599. DumpTunnel(Cache);
  600. #endif
  601. #endif // TUNNELTEST
  602. //
  603. // Clean out the cache, release, and then drop any pool memory we need to
  604. //
  605. FsRtlPruneTunnelCache(Cache, &FreePoolList);
  606. ExReleaseFastMutex(&Cache->Mutex);
  607. FsRtlEmptyFreePoolList(&FreePoolList);
  608. return;
  609. }
  610. //
  611. // *** SPEC
  612. //
  613. // FsRtlFindInTunnelCache - retrieve information from tunnel cache
  614. //
  615. // FsRtlFindInTunnelCache consults the cache to see if an entry with the same
  616. // DirectoryKey and ShortName exist. If so, it returns the data associated with the
  617. // cache entry. The entry may or may not be freed from the cache. Information that is
  618. // stale but not yet purged (older than the retention threshold but not yet cleaned out)
  619. // may be returned.
  620. //
  621. // File systems call FsRtlFindInTunnel cache in the create path when a new file is
  622. // being created and in the rename path when a new name is appearing in a directory.
  623. //
  624. // The caller is required to synchronize this call against FsRtlDeleteTunnelCache.
  625. //
  626. // Arguments:
  627. // Cache a tunnel cache initialized by FsRtlInitializeTunnelCache()
  628. // DirectoryKey ULONG unique ID of the directory where a name is appearing
  629. // Name UNICODE_STRING* name that is being created
  630. // DataLength in length of buffer, out returned length of data found
  631. // Data pointer to buffer
  632. //
  633. // Returns:
  634. // TRUE iff a matching DirectoryKey/Name pair are found, FALSE otherwise
  635. //
  636. // Change: Add out parameters ShortName and LongName to capture the file naming information.
  637. // Plus: this avoids the need for marshalling/unmarshalling steps for the current desired use of
  638. // this code since otherwise we'd have variable length unaligned structures to contain the
  639. // strings along with the other meta-info.
  640. // Minus: Possibly a bad precedent.
  641. //
  642. // Change: spec reads "may or may not be freed from cache" on a hit. This complicates unwinding
  643. // from aborted operations. Data will not be freed on a hit, but will expire like normal entries.
  644. //
  645. BOOLEAN
  646. FsRtlFindInTunnelCache (
  647. IN PTUNNEL Cache,
  648. IN ULONGLONG DirKey,
  649. IN PUNICODE_STRING Name,
  650. OUT PUNICODE_STRING ShortName,
  651. OUT PUNICODE_STRING LongName,
  652. IN OUT PULONG DataLength,
  653. OUT PVOID Data
  654. )
  655. /*++
  656. Routine Description:
  657. Looks up the key
  658. DirKey ## Name
  659. in the tunnel cache and removes it. As a side effect, this routine will initiate
  660. expiration of the aged entries in the tunnel cache.
  661. Arguments:
  662. Cache - a tunnel cache initialized by FsRtlInitializeTunnelCache()
  663. DirKey - the key value of the directory the name will appear in
  664. Name - the name of the entry
  665. ShortName - return string to hold the short name of the tunneled file. Must
  666. already be allocated and large enough for max 8.3 name
  667. LongName - return string to hold the long name of the tunneled file. If
  668. already allocated, may be grown if not large enough. Caller is
  669. responsible for noticing this and freeing data regardless of return value.
  670. DataLength - provides the length of the buffer avaliable to hold the
  671. tunneling information, returns the size of the tunneled information
  672. read out
  673. Return Value:
  674. Boolean true if found, false otherwise
  675. --*/
  676. {
  677. PRTL_SPLAY_LINKS Links;
  678. PTUNNEL_NODE Node;
  679. LONG Compare;
  680. LIST_ENTRY FreePoolList;
  681. BOOLEAN Status = FALSE;
  682. PAGED_CODE();
  683. //
  684. // If MaxEntries is 0 then tunneling is disabled.
  685. //
  686. if (TunnelMaxEntries == 0) return FALSE;
  687. InitializeListHead(&FreePoolList);
  688. #ifdef KEYVIEW
  689. DbgPrint("++\nSearching for %wZ , %08x%08x\n--\n", Name, DblHex64(DirKey));
  690. #endif
  691. ExAcquireFastMutex(&Cache->Mutex);
  692. //
  693. // Expire aged entries first so we don't grab old data
  694. //
  695. FsRtlPruneTunnelCache(Cache, &FreePoolList);
  696. Links = Cache->Cache;
  697. while (Links) {
  698. Node = CONTAINING_RECORD(Links, TUNNEL_NODE, CacheLinks);
  699. Compare = FsRtlCompareNodeAndKey(Node, DirKey, Name);
  700. if (Compare > 0) {
  701. Links = RtlLeftChild(&Node->CacheLinks);
  702. } else {
  703. if (Compare < 0) {
  704. Links = RtlRightChild(&Node->CacheLinks);
  705. } else {
  706. //
  707. // Found tunneling information
  708. //
  709. #if defined(TUNNELTEST) || defined(KEYVIEW)
  710. DbgPrint("FsRtlFindInTunnelCache:\n");
  711. DumpNode(Node, 1);
  712. #ifndef KEYVIEW
  713. DumpTunnel(Cache);
  714. #endif
  715. #endif // TUNNELTEST
  716. break;
  717. }
  718. }
  719. }
  720. try {
  721. if (Links) {
  722. //
  723. // Copy node data into caller's area
  724. //
  725. ASSERT(ShortName->MaximumLength >= (8+1+3)*sizeof(WCHAR));
  726. RtlCopyUnicodeString(ShortName, &Node->ShortName);
  727. if (LongName->MaximumLength >= Node->LongName.Length) {
  728. RtlCopyUnicodeString(LongName, &Node->LongName);
  729. } else {
  730. //
  731. // Need to allocate more memory for the long name
  732. //
  733. LongName->Buffer = FsRtlAllocatePoolWithTag(PagedPool, Node->LongName.Length, '4nuT');
  734. LongName->Length = LongName->MaximumLength = Node->LongName.Length;
  735. RtlCopyMemory(LongName->Buffer, Node->LongName.Buffer, Node->LongName.Length);
  736. }
  737. ASSERT(*DataLength >= Node->TunnelDataLength);
  738. RtlCopyMemory(Data, Node->TunnelData, Node->TunnelDataLength);
  739. *DataLength = Node->TunnelDataLength;
  740. Status = TRUE;
  741. }
  742. } finally {
  743. ExReleaseFastMutex(&Cache->Mutex);
  744. FsRtlEmptyFreePoolList(&FreePoolList);
  745. }
  746. return Status;
  747. }
  748. //
  749. // *** SPEC
  750. //
  751. // FsRtlDeleteKeyFromTunnelCache - delete all cached information associated with
  752. // a DirectoryKey
  753. //
  754. // When file systems delete a directory, all cached information relating to that directory
  755. // must be purged. File systems call FsRtlDeleteKeyFromTunnelCache in the rmdir path.
  756. //
  757. // The caller is required to synchronize this call against FsRtlDeleteTunnelCache.
  758. //
  759. // Arguments:
  760. // Cache a tunnel cache initialized by FsRtlInitializeTunnelCache()
  761. // DirectoryKey ULONGLONG unique ID of the directory that is being deleted
  762. //
  763. VOID
  764. FsRtlDeleteKeyFromTunnelCache (
  765. IN PTUNNEL Cache,
  766. IN ULONGLONG DirKey
  767. )
  768. /*++
  769. Routine Description:
  770. Deletes all entries in the cache associated with a specific directory
  771. Arguments:
  772. Cache - a tunnel cache initialized by FsRtlInitializeTunnelCache()
  773. DirKey - the key value of the directory (presumeably being removed)
  774. Return Value:
  775. None
  776. --*/
  777. {
  778. PRTL_SPLAY_LINKS Links;
  779. PRTL_SPLAY_LINKS SuccessorLinks;
  780. PTUNNEL_NODE Node;
  781. LIST_ENTRY FreePoolList;
  782. PRTL_SPLAY_LINKS LastLinks = NULL;
  783. BOOLEAN Splay = TRUE;
  784. PAGED_CODE();
  785. //
  786. // If MaxEntries is 0 then tunneling is disabled.
  787. //
  788. if (TunnelMaxEntries == 0) return;
  789. InitializeListHead(&FreePoolList);
  790. #ifdef KEYVIEW
  791. DbgPrint("++\nDeleting key %08x%08x\n--\n", DblHex64(DirKey));
  792. #endif
  793. ExAcquireFastMutex(&Cache->Mutex);
  794. Links = Cache->Cache;
  795. while (Links) {
  796. Node = CONTAINING_RECORD(Links, TUNNEL_NODE, CacheLinks);
  797. if (Node->DirKey > DirKey) {
  798. //
  799. // All nodes to the right are bigger, go left
  800. //
  801. Links = RtlLeftChild(&Node->CacheLinks);
  802. } else {
  803. if (Node->DirKey < DirKey) {
  804. if (LastLinks) {
  805. //
  806. // If we have previously seen a candidate node to delete
  807. // and we have now gone too far left - we know where to start.
  808. //
  809. break;
  810. }
  811. Links = RtlRightChild(&Node->CacheLinks);
  812. } else {
  813. //
  814. // Node is a candidate to be deleted, but we might have more nodes
  815. // to the left in the tree. Note this location and go on.
  816. //
  817. LastLinks = Links;
  818. Links = RtlLeftChild(&Node->CacheLinks);
  819. }
  820. }
  821. }
  822. for (Links = LastLinks;
  823. Links;
  824. Links = SuccessorLinks) {
  825. SuccessorLinks = RtlRealSuccessor(Links);
  826. Node = CONTAINING_RECORD(Links, TUNNEL_NODE, CacheLinks);
  827. if (Node->DirKey != DirKey) {
  828. //
  829. // Reached nodes which have a different key, so we're done
  830. //
  831. break;
  832. }
  833. FsRtlRemoveNodeFromTunnel(Cache, Node, &FreePoolList, &Splay);
  834. }
  835. #ifdef TUNNELTEST
  836. DbgPrint("FsRtlDeleteKeyFromTunnelCache:\n");
  837. #ifndef KEYVIEW
  838. DumpTunnel(Cache);
  839. #endif
  840. #endif // TUNNELTEST
  841. ExReleaseFastMutex(&Cache->Mutex);
  842. //
  843. // Free delayed pool
  844. //
  845. FsRtlEmptyFreePoolList(&FreePoolList);
  846. return;
  847. }
  848. //
  849. // *** SPEC
  850. //
  851. // FsRtlDeleteTunnelCache - free a tunnel cache
  852. //
  853. // FsRtlDeleteTunnelCache deletes all cached information. The Cache is no longer
  854. // valid.
  855. //
  856. // Arguments:
  857. // Cache a tunnel cache initialized by FsRtlInitializeTunnelCache()
  858. //
  859. VOID
  860. FsRtlDeleteTunnelCache (
  861. IN PTUNNEL Cache
  862. )
  863. /*++
  864. Routine Description:
  865. Deletes a tunnel cache
  866. Arguments:
  867. Cache - the cache to delete, initialized by FsRtlInitializeTunnelCache()
  868. Return Value:
  869. None
  870. --*/
  871. {
  872. PTUNNEL_NODE Node;
  873. PLIST_ENTRY Link, Next;
  874. PAGED_CODE();
  875. //
  876. // If MaxEntries is 0 then tunneling is disabled.
  877. //
  878. if (TunnelMaxEntries == 0) return;
  879. //
  880. // Zero out the cache and delete everything on the timer list
  881. //
  882. Cache->Cache = NULL;
  883. Cache->NumEntries = 0;
  884. for (Link = Cache->TimerQueue.Flink;
  885. Link != &Cache->TimerQueue;
  886. Link = Next) {
  887. Next = Link->Flink;
  888. Node = CONTAINING_RECORD(Link, TUNNEL_NODE, ListLinks);
  889. FsRtlFreeTunnelNode(Node, NULL);
  890. }
  891. InitializeListHead(&Cache->TimerQueue);
  892. return;
  893. }
  894. VOID
  895. FsRtlPruneTunnelCache (
  896. IN PTUNNEL Cache,
  897. IN OUT PLIST_ENTRY FreePoolList
  898. )
  899. /*++
  900. Routine Description:
  901. Removes deadwood entries from the tunnel cache as defined by TunnelMaxAge and TunnelMaxEntries.
  902. Pool memory is returned on a list for deletion by the calling routine at a time of
  903. its choosing.
  904. For performance reasons we don't want to force freeing of memory inside a mutex.
  905. Arguments:
  906. Cache - the tunnel cache to prune
  907. FreePoolList - a list to queue pool memory on to
  908. Return Value:
  909. None
  910. --*/
  911. {
  912. PTUNNEL_NODE Node;
  913. LARGE_INTEGER ExpireTime;
  914. LARGE_INTEGER CurrentTime;
  915. BOOLEAN Splay = TRUE;
  916. PAGED_CODE();
  917. //
  918. // Calculate the age of the oldest entry we want to keep
  919. //
  920. KeQuerySystemTime(&CurrentTime);
  921. ExpireTime.QuadPart = CurrentTime.QuadPart - TunnelMaxAge;
  922. //
  923. // Expire old entries off of the timer queue. We have to check
  924. // for future time because the clock may jump as a result of
  925. // hard clock change. If we did not do this, a rogue entry
  926. // with a future time could sit at the top of the queue and
  927. // prevent entries from going away.
  928. //
  929. while (!IsListEmpty(&Cache->TimerQueue)) {
  930. Node = CONTAINING_RECORD(Cache->TimerQueue.Flink, TUNNEL_NODE, ListLinks);
  931. if (Node->CreateTime.QuadPart < ExpireTime.QuadPart ||
  932. Node->CreateTime.QuadPart > CurrentTime.QuadPart) {
  933. #if defined(TUNNELTEST) || defined(KEYVIEW)
  934. DbgPrint("Expiring node %x (%ud%ud 1/10 msec too old)\n", Node, DblHex64(ExpireTime.QuadPart - Node->CreateTime.QuadPart));
  935. #endif // TUNNELTEST
  936. FsRtlRemoveNodeFromTunnel(Cache, Node, FreePoolList, &Splay);
  937. } else {
  938. //
  939. // No more nodes to be expired
  940. //
  941. break;
  942. }
  943. }
  944. //
  945. // Remove entries until we're under the TunnelMaxEntries limit
  946. //
  947. while (Cache->NumEntries > TunnelMaxEntries) {
  948. Node = CONTAINING_RECORD(Cache->TimerQueue.Flink, TUNNEL_NODE, ListLinks);
  949. #if defined(TUNNELTEST) || defined(KEYVIEW)
  950. DbgPrint("Dumping node %x (%d > %d)\n", Node, Cache->NumEntries, TunnelMaxEntries);
  951. #endif // TUNNELTEST
  952. FsRtlRemoveNodeFromTunnel(Cache, Node, FreePoolList, &Splay);
  953. }
  954. return;
  955. }
  956. NTSTATUS
  957. FsRtlGetTunnelParameterValue (
  958. IN PUNICODE_STRING ValueName,
  959. IN OUT PULONG Value
  960. )
  961. /*++
  962. Routine Description:
  963. Given a unicode value name this routine will go into the registry
  964. location for the Tunnel parameter information and get the
  965. value.
  966. Arguments:
  967. ValueName - the unicode name for the registry value located in the
  968. double space configuration location of the registry.
  969. Value - a pointer to the ULONG for the result.
  970. Return Value:
  971. NTSTATUS
  972. If STATUS_SUCCESSFUL is returned, the location *Value will be
  973. updated with the DWORD value from the registry. If any failing
  974. status is returned, this value is untouched.
  975. --*/
  976. {
  977. HANDLE Handle;
  978. NTSTATUS Status;
  979. ULONG RequestLength;
  980. ULONG ResultLength;
  981. UCHAR Buffer[KEY_WORK_AREA];
  982. UNICODE_STRING KeyName;
  983. OBJECT_ATTRIBUTES ObjectAttributes;
  984. PKEY_VALUE_FULL_INFORMATION KeyValueInformation;
  985. KeyName.Buffer = TUNNEL_KEY_NAME;
  986. KeyName.Length = sizeof(TUNNEL_KEY_NAME) - sizeof(WCHAR);
  987. KeyName.MaximumLength = sizeof(TUNNEL_KEY_NAME);
  988. InitializeObjectAttributes(&ObjectAttributes,
  989. &KeyName,
  990. OBJ_CASE_INSENSITIVE,
  991. NULL,
  992. NULL);
  993. Status = ZwOpenKey(&Handle,
  994. KEY_READ,
  995. &ObjectAttributes);
  996. if (!NT_SUCCESS(Status)) {
  997. return Status;
  998. }
  999. RequestLength = KEY_WORK_AREA;
  1000. KeyValueInformation = (PKEY_VALUE_FULL_INFORMATION)Buffer;
  1001. while (1) {
  1002. Status = ZwQueryValueKey(Handle,
  1003. ValueName,
  1004. KeyValueFullInformation,
  1005. KeyValueInformation,
  1006. RequestLength,
  1007. &ResultLength);
  1008. ASSERT( Status != STATUS_BUFFER_OVERFLOW );
  1009. if (Status == STATUS_BUFFER_OVERFLOW) {
  1010. //
  1011. // Try to get a buffer big enough.
  1012. //
  1013. if (KeyValueInformation != (PKEY_VALUE_FULL_INFORMATION)Buffer) {
  1014. ExFreePool(KeyValueInformation);
  1015. }
  1016. RequestLength += 256;
  1017. KeyValueInformation = (PKEY_VALUE_FULL_INFORMATION)
  1018. ExAllocatePoolWithTag(PagedPool,
  1019. RequestLength,
  1020. 'KnuT');
  1021. if (!KeyValueInformation) {
  1022. return STATUS_NO_MEMORY;
  1023. }
  1024. } else {
  1025. break;
  1026. }
  1027. }
  1028. ZwClose(Handle);
  1029. if (NT_SUCCESS(Status)) {
  1030. if (KeyValueInformation->DataLength != 0) {
  1031. PULONG DataPtr;
  1032. //
  1033. // Return contents to the caller.
  1034. //
  1035. DataPtr = (PULONG)
  1036. ((PUCHAR)KeyValueInformation + KeyValueInformation->DataOffset);
  1037. *Value = *DataPtr;
  1038. } else {
  1039. //
  1040. // Treat as if no value was found
  1041. //
  1042. Status = STATUS_OBJECT_NAME_NOT_FOUND;
  1043. }
  1044. }
  1045. if (KeyValueInformation != (PKEY_VALUE_FULL_INFORMATION)Buffer) {
  1046. ExFreePool(KeyValueInformation);
  1047. }
  1048. return Status;
  1049. }
  1050. #if defined(TUNNELTEST) || defined(KEYVIEW)
  1051. VOID
  1052. DumpTunnel (
  1053. PTUNNEL Tunnel
  1054. )
  1055. {
  1056. PRTL_SPLAY_LINKS SplayLinks, Ptr;
  1057. PTUNNEL_NODE Node;
  1058. PLIST_ENTRY Link;
  1059. ULONG Indent = 1, i;
  1060. ULONG EntryCount = 0;
  1061. BOOLEAN CountOff = FALSE;
  1062. DbgPrint("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
  1063. DbgPrint("NumEntries = %d\n", Tunnel->NumEntries);
  1064. DbgPrint("****** Cache Tree\n");
  1065. SplayLinks = Tunnel->Cache;
  1066. if (SplayLinks == NULL) {
  1067. goto end;
  1068. }
  1069. while (RtlLeftChild(SplayLinks) != NULL) {
  1070. SplayLinks = RtlLeftChild(SplayLinks);
  1071. Indent++;
  1072. }
  1073. while (SplayLinks) {
  1074. Node = CONTAINING_RECORD( SplayLinks, TUNNEL_NODE, CacheLinks );
  1075. EntryCount++;
  1076. DumpNode(Node, Indent);
  1077. Ptr = SplayLinks;
  1078. /*
  1079. first check to see if there is a right subtree to the input link
  1080. if there is then the real successor is the left most node in
  1081. the right subtree. That is find and return P in the following diagram
  1082. Links
  1083. \
  1084. .
  1085. .
  1086. .
  1087. /
  1088. P
  1089. \
  1090. */
  1091. if ((Ptr = RtlRightChild(SplayLinks)) != NULL) {
  1092. Indent++;
  1093. while (RtlLeftChild(Ptr) != NULL) {
  1094. Indent++;
  1095. Ptr = RtlLeftChild(Ptr);
  1096. }
  1097. SplayLinks = Ptr;
  1098. } else {
  1099. /*
  1100. we do not have a right child so check to see if have a parent and if
  1101. so find the first ancestor that we are a left decendent of. That
  1102. is find and return P in the following diagram
  1103. P
  1104. /
  1105. .
  1106. .
  1107. .
  1108. Links
  1109. */
  1110. Ptr = SplayLinks;
  1111. while (RtlIsRightChild(Ptr)) {
  1112. Indent--;
  1113. Ptr = RtlParent(Ptr);
  1114. }
  1115. if (!RtlIsLeftChild(Ptr)) {
  1116. //
  1117. // we do not have a real successor so we simply return
  1118. // NULL
  1119. //
  1120. SplayLinks = NULL;
  1121. } else {
  1122. Indent--;
  1123. SplayLinks = RtlParent(Ptr);
  1124. }
  1125. }
  1126. }
  1127. end:
  1128. if (CountOff = (EntryCount != Tunnel->NumEntries)) {
  1129. DbgPrint("!!!!!!!!!! Splay Tree Count Mismatch (%d != %d)\n", EntryCount, Tunnel->NumEntries);
  1130. }
  1131. EntryCount = 0;
  1132. DbgPrint("****** Timer Queue\n");
  1133. for (Link = Tunnel->TimerQueue.Flink;
  1134. Link != &Tunnel->TimerQueue;
  1135. Link = Link->Flink) {
  1136. Node = CONTAINING_RECORD( Link, TUNNEL_NODE, ListLinks );
  1137. EntryCount++;
  1138. DumpNode(Node, 1);
  1139. }
  1140. if (CountOff |= (EntryCount != Tunnel->NumEntries)) {
  1141. DbgPrint("!!!!!!!!!! Timer Queue Count Mismatch (%d != %d)\n", EntryCount, Tunnel->NumEntries);
  1142. }
  1143. ASSERT(!CountOff);
  1144. DbgPrint("------------------------------------------------------------------\n");
  1145. }
  1146. #define MAXINDENT 128
  1147. #define INDENTSTEP 3
  1148. VOID
  1149. DumpNode (
  1150. PTUNNEL_NODE Node,
  1151. ULONG Indent
  1152. )
  1153. {
  1154. ULONG i;
  1155. CHAR SpaceBuf[MAXINDENT*INDENTSTEP + 1];
  1156. Indent--;
  1157. if (Indent > MAXINDENT) {
  1158. Indent = MAXINDENT;
  1159. }
  1160. //
  1161. // DbgPrint is really expensive to iteratively call to do the indenting,
  1162. // so just build up the indentation all at once on the stack.
  1163. //
  1164. RtlFillMemory(SpaceBuf, Indent*INDENTSTEP, ' ');
  1165. SpaceBuf[Indent*INDENTSTEP] = '\0';
  1166. DbgPrint("%sNode 0x%x CreateTime = %08x%08x, DirKey = %08x%08x, Flags = %d\n",
  1167. SpaceBuf,
  1168. Node,
  1169. DblHex64(Node->CreateTime.QuadPart),
  1170. DblHex64(Node->DirKey),
  1171. Node->Flags );
  1172. DbgPrint("%sShort = %wZ, Long = %wZ\n", SpaceBuf,
  1173. &Node->ShortName,
  1174. &Node->LongName );
  1175. DbgPrint("%sP = %x, R = %x, L = %x\n", SpaceBuf,
  1176. RtlParent(&Node->CacheLinks),
  1177. RtlRightChild(&Node->CacheLinks),
  1178. RtlLeftChild(&Node->CacheLinks) );
  1179. }
  1180. #endif // TUNNELTEST