Author: pschweitzer Date: Sun May 24 17:42:05 2015 New Revision: 67886
URL: http://svn.reactos.org/svn/reactos?rev=67886&view=rev Log: [NTOSKRNL] Short commit message: implementation of (names) tunnel cache in file system RTL. This is in the vast majority work done by Johannes Anderwald, I've just reviewed, fixed a few things, and implemented last bits.
Thanks to Johannes for his initial implementation (and huge work!). Dedicated to Hervé's secret plans ;-).
CORE-7272 CORE-3875
Modified: trunk/reactos/ntoskrnl/fsrtl/fsrtlpc.c trunk/reactos/ntoskrnl/fsrtl/tunnel.c trunk/reactos/ntoskrnl/include/internal/fsrtl.h
Modified: trunk/reactos/ntoskrnl/fsrtl/fsrtlpc.c URL: http://svn.reactos.org/svn/reactos/trunk/reactos/ntoskrnl/fsrtl/fsrtlpc.c?re... ============================================================================== --- trunk/reactos/ntoskrnl/fsrtl/fsrtlpc.c [iso-8859-1] (original) +++ trunk/reactos/ntoskrnl/fsrtl/fsrtlpc.c [iso-8859-1] Sun May 24 17:42:05 2015 @@ -170,6 +170,7 @@ IFS_POOL_TAG, 0);
+ FsRtlInitializeTunnels(); FsRtlInitializeLargeMcbs();
/* Allocate the Resource Buffer */
Modified: trunk/reactos/ntoskrnl/fsrtl/tunnel.c URL: http://svn.reactos.org/svn/reactos/trunk/reactos/ntoskrnl/fsrtl/tunnel.c?rev... ============================================================================== --- trunk/reactos/ntoskrnl/fsrtl/tunnel.c [iso-8859-1] (original) +++ trunk/reactos/ntoskrnl/fsrtl/tunnel.c [iso-8859-1] Sun May 24 17:42:05 2015 @@ -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 ******************************************************************/ @@ -12,11 +13,298 @@ #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 = (PTUNNEL_NODE_ENTRY)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 = (PTUNNEL_NODE_ENTRY)CONTAINING_RECORD(Entry, TUNNEL_NODE_ENTRY, TimerQueueEntry); + FsRtlRemoveNodeFromTunnel(Cache, CurEntry, PoolList, &Rebalance); + } +} + +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); +} + +VOID +NTAPI +FsRtlInitializeTunnels() +{ + 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, 0 /*FIXME*/, 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 = (PTUNNEL_NODE_ENTRY)CONTAINING_RECORD(CurEntry, TUNNEL_NODE_ENTRY, TimerQueueEntry); + FsRtlFreeTunnelNode(CurNode, 0); + } +} + /* PUBLIC FUNCTIONS **********************************************************/
/*++ * @name FsRtlAddToTunnelCache - * @unimplemented + * @implemented * * FILLME * @@ -56,13 +344,228 @@ IN ULONG DataLength, IN PVOID Data) { - /* Unimplemented */ - KeBugCheck(FILE_SYSTEM); + PTUNNEL_NODE_ENTRY NodeEntry; + 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) + { + /* bigger than default entry */ + NodeEntry = ExAllocatePool(NonPagedPool, Length); + AllocatedFromPool = TRUE; + } + else + { + /* get standard entry */ + NodeEntry = ExAllocateFromPagedLookasideList(&TunnelLookasideList); + } + + /* check for success */ + if (!NodeEntry) + { + /* out of memory */ + return; + } + + /* 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 (LastEntry->LeftChild == CurEntry) + { + RtlInsertAsLeftChild(LastEntry, NodeEntry); + } + else + { + RtlInsertAsRightChild(LastEntry, NodeEntry); + } + } + + /* remove entry */ + RemoveEntryList(&((PTUNNEL_NODE_ENTRY)LastEntry)->TimerQueueEntry); + + /* free node entry */ + FsRtlFreeTunnelNode((PTUNNEL_NODE_ENTRY)LastEntry, &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 +585,92 @@ 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 = (PTUNNEL_NODE_ENTRY)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 = (PTUNNEL_NODE_ENTRY)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,12 +686,48 @@ NTAPI FsRtlDeleteTunnelCache(IN PTUNNEL Cache) { - UNIMPLEMENTED; + 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 = (PTUNNEL_NODE_ENTRY)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 * @@ -149,14 +767,111 @@ 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 * @@ -172,7 +887,19 @@ NTAPI FsRtlInitializeTunnelCache(IN PTUNNEL Cache) { - UNIMPLEMENTED; + 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 */
Modified: trunk/reactos/ntoskrnl/include/internal/fsrtl.h URL: http://svn.reactos.org/svn/reactos/trunk/reactos/ntoskrnl/include/internal/f... ============================================================================== --- trunk/reactos/ntoskrnl/include/internal/fsrtl.h [iso-8859-1] (original) +++ trunk/reactos/ntoskrnl/include/internal/fsrtl.h [iso-8859-1] Sun May 24 17:42:05 2015 @@ -114,6 +114,12 @@ VOID );
+VOID +NTAPI +FsRtlInitializeTunnels( + VOID +); + // // File contexts Routines //