[NTOSKRNL] In FsRtlAddToTunnelCache() allocate memory from PagedPool when required.
[reactos.git] / ntoskrnl / fsrtl / tunnel.c
index 63cfffe..545cf2f 100644 (file)
@@ -3,7 +3,8 @@
  * LICENSE:         GPL - See COPYING in the top level directory
  * FILE:            ntoskrnl/fsrtl/tunnel.c
  * PURPOSE:         Provides the Tunnel Cache implementation for file system drivers.
- * PROGRAMMERS:     None.
+ * PROGRAMMERS:     Johannes Anderwald (johannes.anderwald@reactos.org)
+ *                  Pierre Schweitzer (pierre@reactos.org)
  */
 
 /* INCLUDES ******************************************************************/
 #define NDEBUG
 #include <debug.h>
 
+typedef struct {
+    RTL_SPLAY_LINKS SplayInfo;
+    LIST_ENTRY TimerQueueEntry;
+    LARGE_INTEGER Time;
+    ULONGLONG DirectoryKey;
+    ULONG Flags;
+    UNICODE_STRING LongName;
+    UNICODE_STRING ShortName;
+    PVOID Data;
+    ULONG DataLength;
+} TUNNEL_NODE_ENTRY, *PTUNNEL_NODE_ENTRY;
+
+ULONG TunnelMaxEntries = 256;
+ULONG TunnelMaxAge = 15;
+PAGED_LOOKASIDE_LIST TunnelLookasideList;
+
+#define DEFAULT_EXTRA_SIZE (72)
+#define DEFAULT_ENTRY_SIZE (sizeof(TUNNEL_NODE_ENTRY) + DEFAULT_EXTRA_SIZE)
+
+#define TUNNEL_FLAG_POOL 0x2
+#define TUNNEL_FLAG_KEY_SHORT_NAME 0x1
+
+VOID
+FsRtlFreeTunnelNode(
+    IN PTUNNEL_NODE_ENTRY CurEntry,
+    IN PLIST_ENTRY PoolList OPTIONAL)
+{
+    if (PoolList)
+    {
+        /* divert the linked list entry, it's not required anymore, but we need it */ 
+        InsertHeadList(PoolList, &CurEntry->TimerQueueEntry);
+        return;
+    }
+
+    if (CurEntry->Flags & TUNNEL_FLAG_POOL)
+        ExFreePool(CurEntry);
+    else
+        ExFreeToPagedLookasideList(&TunnelLookasideList, CurEntry);
+}
+
+VOID
+FsRtlRemoveNodeFromTunnel(
+    IN PTUNNEL Cache,
+    IN PTUNNEL_NODE_ENTRY CurEntry,
+    IN PLIST_ENTRY PoolList,
+    OUT PBOOLEAN Rebalance)
+{
+    /* delete entry and rebalance if required */
+    if (Rebalance && *Rebalance)
+    {
+        Cache->Cache = RtlDelete(&CurEntry->SplayInfo);
+        /* reset */
+        *Rebalance = FALSE;
+    }
+    else
+    {
+        RtlDeleteNoSplay(&CurEntry->SplayInfo, &Cache->Cache);
+    }
+
+    /* remove entry */
+    RemoveEntryList(&CurEntry->TimerQueueEntry);
+
+    /* free node entry */
+    FsRtlFreeTunnelNode(CurEntry, PoolList);
+
+    /* decrement node count */
+    Cache->NumEntries--;
+}
+
+VOID
+FsRtlPruneTunnelCache(
+    IN PTUNNEL Cache,
+    IN PLIST_ENTRY PoolList)
+{
+    PLIST_ENTRY Entry, NextEntry;
+    PTUNNEL_NODE_ENTRY CurEntry;
+    LARGE_INTEGER CurTime, OldTime;
+    BOOLEAN Rebalance = TRUE;
+    PAGED_CODE();
+
+    /* query time */
+    KeQuerySystemTime(&CurTime);
+
+    /* subtract maximum node age */
+    OldTime.QuadPart = CurTime.QuadPart - TunnelMaxAge;
+
+    /* free all entries */
+    Entry = Cache->TimerQueue.Flink;
+
+    while(Entry != &Cache->TimerQueue)
+    {
+        /* get node entry */
+        CurEntry = CONTAINING_RECORD(Entry, TUNNEL_NODE_ENTRY, TimerQueueEntry);
+
+        /* get next entry */
+         NextEntry = Entry->Flink;
+
+        /* prune if expired OR if in advance in time */
+        if (CurEntry->Time.QuadPart < OldTime.QuadPart ||
+            CurEntry->Time.QuadPart > CurTime.QuadPart)
+        {
+            FsRtlRemoveNodeFromTunnel(Cache, CurEntry, PoolList, &Rebalance);
+        }
+
+        /* move to next entry */
+        Entry = NextEntry;
+    }
+
+    /* If we have too many entries */
+    while (Cache->NumEntries > TunnelMaxEntries)
+    {
+        CurEntry = CONTAINING_RECORD(Entry, TUNNEL_NODE_ENTRY, TimerQueueEntry);
+        FsRtlRemoveNodeFromTunnel(Cache, CurEntry, PoolList, &Rebalance);
+    }
+}
+
+INIT_FUNCTION
+VOID
+FsRtlGetTunnelParameterValue(
+    IN PUNICODE_STRING ParameterName,
+    OUT PULONG Value)
+{
+    UNICODE_STRING Root = RTL_CONSTANT_STRING(L"Registry\\Machine\\System\\CurrentControlSet\\Control\\FileSystem");
+    OBJECT_ATTRIBUTES ObjectAttributes;
+    HANDLE hKey;
+    NTSTATUS Status;
+    ULONG Length;
+    PKEY_VALUE_FULL_INFORMATION Info;
+
+    /* initialize object attributes */
+    InitializeObjectAttributes(&ObjectAttributes, &Root, OBJ_CASE_INSENSITIVE, NULL, NULL);
+
+    /* open registry key */
+    Status = ZwOpenKey(&hKey, KEY_READ, &ObjectAttributes);
+
+    if (!NT_SUCCESS(Status))
+    {
+        /* failed to open key */
+        return;
+    }
+
+    /* query value size */
+    Status = ZwQueryValueKey(hKey, ParameterName, KeyValueFullInformation, NULL, 0, &Length);
+
+    if (Status != STATUS_BUFFER_TOO_SMALL)
+    {
+        /* failed to query size */
+        ZwClose(hKey);
+        return;
+    }
+
+    /* allocate buffer */
+    Info = ExAllocatePool(PagedPool, Length);
+
+    if (!Info)
+    {
+         /* out of memory */
+        ZwClose(hKey);
+        return;
+    }
+
+    /* query value */
+    Status = ZwQueryValueKey(hKey, ParameterName, KeyValueFullInformation, NULL, 0, &Length);
+
+    if (NT_SUCCESS(Status))
+    {
+        if (Info->DataLength)
+        {
+            /* store result */
+            *Value = (ULONG)((ULONG_PTR)Info + Info->DataOffset);
+        }
+    }
+
+    /* free buffer */
+    ExFreePool(Info);
+
+    /* close key */
+    ZwClose(hKey);
+}
+
+INIT_FUNCTION
+VOID
+NTAPI
+FsRtlInitializeTunnels(VOID)
+{
+    ULONG TunnelEntries;
+    UNICODE_STRING MaximumTunnelEntryAgeInSeconds = RTL_CONSTANT_STRING(L"MaximumTunnelEntryAgeInSeconds");
+    UNICODE_STRING MaximumTunnelEntries = RTL_CONSTANT_STRING( L"MaximumTunnelEntries");
+
+    /* check for nt */
+    if (MmIsThisAnNtAsSystem())
+    {
+        /* default */
+        TunnelMaxEntries = 1024;
+    }
+
+    /* check for custom override of max entries*/
+    FsRtlGetTunnelParameterValue(&MaximumTunnelEntries, &TunnelMaxEntries);
+
+    /* check for custom override of age*/
+    FsRtlGetTunnelParameterValue(&MaximumTunnelEntryAgeInSeconds, &TunnelMaxAge);
+
+    if (!TunnelMaxAge)
+    {
+        /* no age means no entries */
+        TunnelMaxEntries = 0;
+    }
+
+    /* get max entries */
+    TunnelEntries = TunnelMaxEntries;
+
+    /* convert to ticks */
+    TunnelMaxAge *= 10000000;
+
+    if(TunnelMaxEntries <= 65535)
+    {
+        /* use max 256 entries */
+        TunnelEntries = TunnelMaxEntries / 16;
+    }
+
+    if(!TunnelEntries && TunnelMaxEntries )
+    {
+        /* max tunnel entries was too small */
+        TunnelEntries = TunnelMaxEntries + 1;
+    }
+
+    if (TunnelEntries > 0xFFFF)
+    {
+        /* max entries is 256 */
+        TunnelEntries = 256;
+    }
+
+    /* initialize look aside list */
+    ExInitializePagedLookasideList(&TunnelLookasideList, NULL, NULL, 0, DEFAULT_ENTRY_SIZE, 'TunL', TunnelEntries);
+}
+
+LONG
+FsRtlCompareNodeAndKey(
+    IN PTUNNEL_NODE_ENTRY CurEntry,
+    IN ULONGLONG DirectoryKey,
+    IN PUNICODE_STRING KeyString)
+{
+    PUNICODE_STRING String;
+    LONG Ret;
+
+    if (DirectoryKey > CurEntry->DirectoryKey)
+    {
+        Ret = 1;
+    }
+    else if (DirectoryKey < CurEntry->DirectoryKey)
+    {
+        Ret = -1;
+    }
+    else
+    {
+        if (CurEntry->Flags & TUNNEL_FLAG_KEY_SHORT_NAME)
+        {
+            /* use short name as key */
+            String = &CurEntry->ShortName;
+        }
+        else
+        {
+            /* use long name as key */
+            String = &CurEntry->LongName;
+        }
+
+        Ret = RtlCompareUnicodeString(KeyString, String, TRUE);
+    }
+
+    return Ret;
+}
+
+VOID
+FsRtlEmptyFreePoolList(
+    IN PLIST_ENTRY PoolList)
+{
+    PLIST_ENTRY CurEntry;
+    PTUNNEL_NODE_ENTRY CurNode;
+
+    /* loop over all the entry */
+    while (!IsListEmpty(PoolList))
+    {
+        /* and free them, one by one */
+        CurEntry = RemoveHeadList(PoolList);
+        CurNode = CONTAINING_RECORD(CurEntry, TUNNEL_NODE_ENTRY, TimerQueueEntry);
+        FsRtlFreeTunnelNode(CurNode, 0);
+    }
+}
+
 /* PUBLIC FUNCTIONS **********************************************************/
 
 /*++
  * @name FsRtlAddToTunnelCache
- * @unimplemented
+ * @implemented
  *
  * FILLME
  *
@@ -56,13 +346,229 @@ FsRtlAddToTunnelCache(IN PTUNNEL Cache,
                       IN ULONG DataLength,
                       IN PVOID Data)
 {
-    /* Unimplemented */
-    KeBugCheck(FILE_SYSTEM);
+    PTUNNEL_NODE_ENTRY NodeEntry = NULL;
+    PRTL_SPLAY_LINKS CurEntry, LastEntry;
+    ULONG Length;
+    LONG Result = 0;
+    BOOLEAN AllocatedFromPool = FALSE;
+    PUNICODE_STRING KeyString;
+    LIST_ENTRY PoolList;
+
+    PAGED_CODE();
+
+    /* check if tunnel cache is enabled */
+    if (!TunnelMaxEntries)
+    {
+        /* entries are disabled */
+        return;
+    }
+
+    /* initialize free pool list */
+    InitializeListHead(&PoolList);
+
+    /* calculate node length */
+    Length = sizeof(TUNNEL_NODE_ENTRY);
+
+    /* add data size */
+    Length += DataLength;
+
+    if (ShortName)
+    {
+        /* add short name length */
+        Length += ShortName->Length;
+    }
+
+    if (LongName)
+    {
+        /* add short name length */
+        Length += LongName->Length;
+    }
+
+    if (Length <= DEFAULT_ENTRY_SIZE)
+    {
+        /* get standard entry */
+        NodeEntry = ExAllocateFromPagedLookasideList(&TunnelLookasideList);
+    }
+
+    if (NodeEntry == NULL)
+    {
+        /* bigger than default entry or allocation failed */
+        NodeEntry = ExAllocatePool(PagedPool | POOL_COLD_ALLOCATION, Length);
+        /* check for success */
+        if (NodeEntry == NULL)
+        {
+             /* out of memory */
+             return;
+        }
+
+        AllocatedFromPool = TRUE;
+    }
+
+    /* acquire lock */
+    ExAcquireFastMutex(&Cache->Mutex);
+
+    /* now search cache for existing entries */
+    CurEntry = Cache->Cache;
+
+    /* check which key should be used for search */
+    KeyString = (KeyByShortName ? ShortName : LongName);
+
+    /* initialize last entry */
+    LastEntry = NULL;
+
+    while(CurEntry)
+    {
+        /* compare current node */
+        Result = FsRtlCompareNodeAndKey((PTUNNEL_NODE_ENTRY)CurEntry, DirectoryKey, KeyString);
+
+        /* backup last entry */
+        LastEntry = CurEntry;
+
+        if (Result > 0)
+        {
+            /* current directory key is bigger */
+            CurEntry = CurEntry->LeftChild;
+        }
+        else
+        {
+            if (Result == 0)
+            {
+                /* found equal entry */
+                break;
+            }
+
+            /* current directory key is smaller */
+            CurEntry = CurEntry->RightChild;
+        }
+    }
+
+    /* initialize node entry */
+    RtlInitializeSplayLinks(&NodeEntry->SplayInfo);
+
+    if (CurEntry != NULL)
+    {
+         /* found existing item */
+         if (CurEntry->LeftChild)
+         {
+             /* update parent */
+             RtlInsertAsLeftChild(NodeEntry, CurEntry->LeftChild);
+         }
+
+         if (CurEntry->RightChild)
+         {
+             /* update parent */
+             RtlInsertAsRightChild(NodeEntry, CurEntry->RightChild);
+         }
+
+         if (CurEntry->Parent == CurEntry)
+         {
+              /* cur entry was root */
+              Cache->Cache = (struct _RTL_SPLAY_LINKS*)NodeEntry;
+         }
+         else
+         {
+              /* update parent node */
+              if (RtlIsLeftChild(CurEntry))
+              {
+                  RtlInsertAsLeftChild(RtlParent(CurEntry), NodeEntry);
+              }
+              else
+              {
+                  RtlInsertAsRightChild(RtlParent(CurEntry), NodeEntry);
+              }
+         }
+         
+         /* remove entry */
+         RemoveEntryList(&((PTUNNEL_NODE_ENTRY)CurEntry)->TimerQueueEntry);
+
+         /* free node entry */
+         FsRtlFreeTunnelNode((PTUNNEL_NODE_ENTRY)CurEntry, &PoolList);
+
+         /* decrement node count */
+         Cache->NumEntries--;
+    }
+    else
+    {
+        if (LastEntry == NULL)
+        {
+            /* first entry in tunnel cache */
+            Cache->Cache = (struct _RTL_SPLAY_LINKS*)NodeEntry;
+        }
+        else
+        {
+            if (Result > 0)
+            {
+                /* new left node */
+                RtlInsertAsLeftChild(LastEntry, NodeEntry);
+            }
+            else
+            {
+                /* new right node */
+                RtlInsertAsRightChild(LastEntry, NodeEntry);
+            }
+        }
+    }
+
+    /* initialize entry */
+    KeQuerySystemTime(&NodeEntry->Time);
+
+    NodeEntry->DirectoryKey = DirectoryKey;
+    NodeEntry->Flags = (AllocatedFromPool ? TUNNEL_FLAG_POOL : 0x0);
+    NodeEntry->Flags |= (KeyByShortName ? TUNNEL_FLAG_KEY_SHORT_NAME : 0x0);
+
+    if (ShortName)
+    {
+        /* copy short name */
+        NodeEntry->ShortName.Length = ShortName->Length;
+        NodeEntry->ShortName.MaximumLength = ShortName->Length;
+        NodeEntry->ShortName.Buffer = (LPWSTR)((ULONG_PTR)NodeEntry + sizeof(TUNNEL_NODE_ENTRY));
+
+        RtlMoveMemory(NodeEntry->ShortName.Buffer, ShortName->Buffer, ShortName->Length);
+    }
+    else
+    {
+        NodeEntry->ShortName.Length = NodeEntry->ShortName.MaximumLength = 0;
+        NodeEntry->ShortName.Buffer = NULL;
+    }
+
+    if (LongName)
+    {
+        /* copy long name */
+        NodeEntry->LongName.Length = LongName->Length;
+        NodeEntry->LongName.MaximumLength = LongName->Length;
+        NodeEntry->LongName.Buffer = (LPWSTR)((ULONG_PTR)NodeEntry + sizeof(TUNNEL_NODE_ENTRY) + NodeEntry->ShortName.Length);
+
+        RtlMoveMemory(NodeEntry->LongName.Buffer, LongName->Buffer, LongName->Length);
+    }
+    else
+    {
+        NodeEntry->LongName.Length = NodeEntry->LongName.MaximumLength = 0;
+        NodeEntry->LongName.Buffer = NULL;
+    }
+
+     NodeEntry->DataLength = DataLength;
+     NodeEntry->Data = (PVOID)((ULONG_PTR)NodeEntry + sizeof(TUNNEL_NODE_ENTRY) + NodeEntry->ShortName.Length + NodeEntry->LongName.Length);
+     RtlMoveMemory(NodeEntry->Data, Data, DataLength);
+
+     /* increment node count */
+     Cache->NumEntries++;
+
+     /* insert into list */
+     InsertTailList(&Cache->TimerQueue, &NodeEntry->TimerQueueEntry);
+
+     /* prune cache */
+     FsRtlPruneTunnelCache(Cache, &PoolList);
+
+     /* release lock */
+     ExReleaseFastMutex(&Cache->Mutex);
+
+     /* free pool list */
+     FsRtlEmptyFreePoolList(&PoolList);
 }
 
 /*++
  * @name FsRtlDeleteKeyFromTunnelCache
- * @unimplemented
+ * @implemented
  *
  * FILLME
  *
@@ -82,13 +588,92 @@ NTAPI
 FsRtlDeleteKeyFromTunnelCache(IN PTUNNEL Cache,
                               IN ULONGLONG DirectoryKey)
 {
-    /* Unimplemented */
-    KeBugCheck(FILE_SYSTEM);
+    BOOLEAN Rebalance = TRUE;
+    LIST_ENTRY PoolList;
+    PTUNNEL_NODE_ENTRY CurNode;
+    PRTL_SPLAY_LINKS CurEntry, LastEntry = NULL, Successors;
+
+    PAGED_CODE();
+
+    /* check if tunnel cache is enabled */
+    if (!TunnelMaxEntries)
+    {
+        /* entries are disabled */
+        return;
+    }
+
+    /* initialize free pool list */
+    InitializeListHead(&PoolList);
+
+    /* acquire lock */
+    ExAcquireFastMutex(&Cache->Mutex);
+
+    /* Look for the entry */
+    CurEntry = Cache->Cache;
+    while (CurEntry)
+    {
+        CurNode = CONTAINING_RECORD(CurEntry, TUNNEL_NODE_ENTRY, SplayInfo);
+
+        if (CurNode->DirectoryKey > DirectoryKey)
+        {
+            /* current directory key is bigger */
+            CurEntry = CurEntry->LeftChild;
+        }
+        else if (CurNode->DirectoryKey < DirectoryKey)
+        {
+            /* if we have already found one suitable, break */
+            if (LastEntry != NULL)
+            {
+                break;
+            }
+
+            /* current directory key is smaller */
+            CurEntry = CurEntry->RightChild;
+        }
+        else
+        {
+            /* save and look for another */
+            LastEntry = CurEntry;
+            CurEntry = CurEntry->LeftChild;
+        }
+    }
+
+    /* was it found? */
+    if (LastEntry == NULL)
+    {
+        /* release tunnel lock */
+        ExReleaseFastMutex(&Cache->Mutex);
+
+        return;
+    }
+
+    /* delete any matching key */
+    do
+    {
+        CurNode = CONTAINING_RECORD(LastEntry, TUNNEL_NODE_ENTRY, SplayInfo);
+
+        Successors = RtlRealSuccessor(LastEntry);
+        if (CurNode->DirectoryKey != DirectoryKey)
+        {
+            break;
+        }
+
+        /* remove from tunnel */
+        FsRtlRemoveNodeFromTunnel(Cache, CurNode, &PoolList, &Rebalance);
+        LastEntry = Successors;
+    }
+    while (LastEntry != NULL);
+
+    /* release tunnel lock */
+    ExReleaseFastMutex(&Cache->Mutex);
+
+    /* free pool */
+    FsRtlEmptyFreePoolList(&PoolList);
 }
 
 /*++
  * @name FsRtlDeleteTunnelCache
- * @unimplemented
+ * @implemented
  *
  * FILLME
  *
@@ -104,13 +689,48 @@ VOID
 NTAPI
 FsRtlDeleteTunnelCache(IN PTUNNEL Cache)
 {
-    /* Unimplemented */
-    KeBugCheck(FILE_SYSTEM);
+    PLIST_ENTRY Entry, NextEntry;
+    PTUNNEL_NODE_ENTRY CurEntry;
+
+    PAGED_CODE();
+
+    /* check if tunnel cache is enabled */
+    if (!TunnelMaxEntries)
+    {
+        /* entries are disabled */
+        return;
+    }
+
+    /* free all entries */
+    Entry = Cache->TimerQueue.Flink;
+
+    while(Entry != &Cache->TimerQueue)
+    {
+        /* get node entry */
+        CurEntry = CONTAINING_RECORD(Entry, TUNNEL_NODE_ENTRY, TimerQueueEntry);
+
+        /* get next entry */
+         NextEntry = Entry->Flink;
+
+        /* remove entry from list */
+        RemoveEntryList(&CurEntry->TimerQueueEntry);
+
+        /* free entry */
+        FsRtlFreeTunnelNode(CurEntry, NULL);
+
+        /* move to next entry */
+        Entry = NextEntry;
+    }
+
+    /* reset object */
+    Cache->Cache = NULL;
+    Cache->NumEntries = 0;
+    InitializeListHead(&Cache->TimerQueue);
 }
 
 /*++
  * @name FsRtlFindInTunnelCache
- * @unimplemented
+ * @implemented
  *
  * FILLME
  *
@@ -150,14 +770,111 @@ FsRtlFindInTunnelCache(IN PTUNNEL Cache,
                        IN OUT PULONG DataLength,
                        OUT PVOID Data)
 {
-    /* Unimplemented */
-    KeBugCheck(FILE_SYSTEM);
-    return FALSE;
+    BOOLEAN Ret = FALSE;
+    PTUNNEL_NODE_ENTRY CurEntry;
+    LIST_ENTRY PoolList;
+    //NTSTATUS Status;
+    LONG Result;
+
+    PAGED_CODE();
+
+    /* check if tunnel cache is enabled */
+    if (!TunnelMaxEntries)
+    {
+        /* entries are disabled */
+        return FALSE;
+    }
+
+    /* initialize free pool list */
+    InitializeListHead(&PoolList);
+
+    /* acquire tunnel lock */
+    ExAcquireFastMutex(&Cache->Mutex);
+
+    /* prune old entries */
+    FsRtlPruneTunnelCache(Cache, &PoolList);
+
+    /* now search cache for existing entries */
+    CurEntry = (PTUNNEL_NODE_ENTRY)Cache->Cache;
+
+    while(CurEntry)
+    {
+        /* compare current node */
+        Result = FsRtlCompareNodeAndKey(CurEntry, DirectoryKey, Name);
+
+        if (Result > 0)
+        {
+            /* current directory key is bigger */
+            CurEntry = (PTUNNEL_NODE_ENTRY)CurEntry->SplayInfo.LeftChild;
+        }
+        else
+        {
+            if (Result == 0)
+            {
+                /* found equal entry */
+                break;
+            }
+
+            /* current directory key is smaller */
+            CurEntry = (PTUNNEL_NODE_ENTRY)CurEntry->SplayInfo.RightChild;
+        }
+    }
+
+    if (CurEntry != NULL)
+    {
+        _SEH2_TRY
+        {
+            /* copy short name */
+            RtlCopyUnicodeString(ShortName, &CurEntry->ShortName);
+
+            /* check size */
+            if (LongName->MaximumLength < CurEntry->LongName.Length)
+            {
+                /* buffer is too small */
+                LongName->Buffer = ExAllocatePool(PagedPool, CurEntry->LongName.Length);
+                if (LongName->Buffer)
+                {
+                    LongName->Length = CurEntry->LongName.Length;
+                    LongName->MaximumLength = CurEntry->LongName.MaximumLength;
+                    RtlMoveMemory(LongName->Buffer, CurEntry->LongName.Buffer, CurEntry->LongName.Length);
+                }
+            }
+            else
+            {
+                /* buffer is big enough */
+                RtlCopyUnicodeString(LongName, &CurEntry->LongName);
+            }
+
+            /* copy data */
+            RtlMoveMemory(Data, CurEntry->Data, CurEntry->DataLength);
+
+            /* store size */
+            *DataLength = CurEntry->DataLength;
+
+            /* done */
+            Ret = TRUE;
+        }
+        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+        {
+            /* Get the status */
+            //Status = _SEH2_GetExceptionCode();
+        }
+        _SEH2_END;
+
+    }
+
+    /* release tunnel lock */
+    ExReleaseFastMutex(&Cache->Mutex);
+
+    /* free pool */
+    FsRtlEmptyFreePoolList(&PoolList);
+
+    return Ret;
 }
 
 /*++
- * @name FsRtlDeleteTunnelCache
- * @unimplemented
+ * @name FsRtlInitializeTunnelCache
+ * @implemented
  *
  * FILLME
  *
@@ -173,8 +890,19 @@ VOID
 NTAPI
 FsRtlInitializeTunnelCache(IN PTUNNEL Cache)
 {
-    /* Unimplemented */
-    KeBugCheck(FILE_SYSTEM);
+    PAGED_CODE();
+
+    /* initialize mutex */
+    ExInitializeFastMutex(&Cache->Mutex);
+
+    /* initialize node tree */
+    Cache->Cache = NULL;
+
+    /* initialize timer list */
+    InitializeListHead(&Cache->TimerQueue);
+
+    /* initialize node count */
+    Cache->NumEntries = 0;
 }
 
 /* EOF */