/* * PROJECT: ReactOS Kernel * 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: Johannes Anderwald (johannes.anderwald@reactos.org) * Pierre Schweitzer (pierre@reactos.org) */ /* INCLUDES ******************************************************************/ #include #define NDEBUG #include 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 * @implemented * * FILLME * * @param Cache * FILLME * * @param DirectoryKey * FILLME * * @param ShortName * FILLME * * @param LongName * FILLME * * @param KeyByShortName * FILLME * * @param DataLength * FILLME * * @param Data * FILLME * * @return None * * @remarks None * *--*/ VOID NTAPI FsRtlAddToTunnelCache(IN PTUNNEL Cache, IN ULONGLONG DirectoryKey, IN PUNICODE_STRING ShortName, IN PUNICODE_STRING LongName, IN BOOLEAN KeyByShortName, IN ULONG DataLength, IN PVOID Data) { 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 * @implemented * * FILLME * * @param Cache * FILLME * * @param DirectoryKey * FILLME * * @return None * * @remarks None * *--*/ VOID NTAPI FsRtlDeleteKeyFromTunnelCache(IN PTUNNEL Cache, IN ULONGLONG DirectoryKey) { 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 * @implemented * * FILLME * * @param Cache * FILLME * * @return None * * @remarks None * *--*/ VOID NTAPI FsRtlDeleteTunnelCache(IN PTUNNEL Cache) { 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 * @implemented * * FILLME * * @param Cache * FILLME * * @param DirectoryKey * FILLME * * @param ShortName * FILLME * * @param LongName * FILLME * * @param KeyByShortName * FILLME * * @param DataLength * FILLME * * @param Data * FILLME * * @return None * * @remarks None * *--*/ BOOLEAN NTAPI FsRtlFindInTunnelCache(IN PTUNNEL Cache, IN ULONGLONG DirectoryKey, IN PUNICODE_STRING Name, OUT PUNICODE_STRING ShortName, OUT PUNICODE_STRING LongName, IN OUT PULONG DataLength, OUT PVOID Data) { 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 FsRtlInitializeTunnelCache * @implemented * * FILLME * * @param Cache * FILLME * * @return None * * @remarks None * *--*/ VOID NTAPI FsRtlInitializeTunnelCache(IN PTUNNEL Cache) { 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 */