1688 lines
41 KiB
C
1688 lines
41 KiB
C
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1995 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
Tunnel.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
The tunnel package provides a set of routines that allow compatibility
|
|||
|
with applications that rely on filesystems being able to "hold onto"
|
|||
|
file meta-info for a short period of time after deletion/renaming and
|
|||
|
reinstantiating a new directory entry with that meta-info if a
|
|||
|
create/rename occurs to cause a file of that name to appear again in a
|
|||
|
short period of time.
|
|||
|
|
|||
|
Note that this violates POSIX rules. This package should not be used
|
|||
|
on POSIX fileobjects, i.e. fileobjects that have case-sensitive names.
|
|||
|
|
|||
|
Entries are keyed by directory and one of the short/long names. An opaque
|
|||
|
rock of information is also associated (create time, last write time, etc.).
|
|||
|
This is expected to vary on a per-filesystem basis.
|
|||
|
|
|||
|
A TUNNEL variable should be initialized for every volume in the system
|
|||
|
at mount time. Thereafter, each delete/rename-out should add to the tunnel
|
|||
|
and each create/rename-in should read from the tunnel. Each directory
|
|||
|
deletion should also notify the package so that all associated entries can
|
|||
|
be flushed. The package is responsible for cleaning out aged entries.
|
|||
|
|
|||
|
Tunneled information is in the paged pool.
|
|||
|
|
|||
|
Concurrent access to the TUNNEL variable is controlled by this package.
|
|||
|
Callers are responsible for synchronizing access to the FsRtlDeleteTunnelCache
|
|||
|
call.
|
|||
|
|
|||
|
The functions provided in this package are as follows:
|
|||
|
|
|||
|
o FsRtlInitializeTunnel - Initializes the TUNNEL package (called once per boot)
|
|||
|
|
|||
|
o FsRtlInitializeTunnelCache - Initializes a TUNNEL structure (called once on mount)
|
|||
|
|
|||
|
o FsRtlAddToTunnelCache - Adds a new key/value pair to the tunnel
|
|||
|
|
|||
|
o FsRtlFindInTunnelCache - Finds and returns a key/value from the tunnel
|
|||
|
|
|||
|
o FsRtlDeleteKeyFromTunnelCache - Deletes all entries with a given
|
|||
|
directory key from the tunnel
|
|||
|
|
|||
|
o FsRtlDeleteTunnelCache - Deletes a TUNNEL structure
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Dan Lovinger [DanLo] 8-Aug-1995
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
#include "FsRtlP.h"
|
|||
|
|
|||
|
#ifndef INLINE
|
|||
|
#define INLINE __inline
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Registry keys/values for controlling tunneling
|
|||
|
//
|
|||
|
|
|||
|
#define TUNNEL_KEY_NAME L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\FileSystem"
|
|||
|
#define TUNNEL_AGE_VALUE_NAME L"MaximumTunnelEntryAgeInSeconds"
|
|||
|
#define TUNNEL_SIZE_VALUE_NAME L"MaximumTunnelEntries"
|
|||
|
#define KEY_WORK_AREA ((sizeof(KEY_VALUE_FULL_INFORMATION) + sizeof(ULONG)) + 64)
|
|||
|
|
|||
|
//
|
|||
|
// Tunnel expiration paramters (cached once at startup)
|
|||
|
//
|
|||
|
|
|||
|
#ifdef ALLOC_DATA_PRAGMA
|
|||
|
#pragma data_seg("PAGEDATA")
|
|||
|
#endif
|
|||
|
ULONG TunnelMaxEntries = 256; // Value for !MmIsThisAnNtAsSystem()
|
|||
|
ULONG TunnelMaxAge = 15;
|
|||
|
#ifdef ALLOC_DATA_PRAGMA
|
|||
|
#pragma data_seg()
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// We use a lookaside list to manage the common size tunnel entry. The common size
|
|||
|
// is contrived to be 128 bytes by adjusting the size we defer for the long name
|
|||
|
// to 16 characters, which is pretty reasonable. If we ever expect to get more than
|
|||
|
// a ULONGLONG data element or common names are observed to become larger, adjusting
|
|||
|
// this may be required.
|
|||
|
//
|
|||
|
|
|||
|
PAGED_LOOKASIDE_LIST TunnelLookasideList;
|
|||
|
#define MAX_LOOKASIDE_DEPTH 256
|
|||
|
|
|||
|
#define LOOKASIDE_NODE_SIZE ( sizeof(TUNNEL_NODE) + \
|
|||
|
sizeof(WCHAR)*(8+1+3) + \
|
|||
|
sizeof(WCHAR)*(16) + \
|
|||
|
sizeof(ULONGLONG) )
|
|||
|
|
|||
|
//
|
|||
|
// Flag bits in the TUNNEL_NODE
|
|||
|
//
|
|||
|
|
|||
|
#define TUNNEL_FLAG_NON_LOOKASIDE 0x1
|
|||
|
#define TUNNEL_FLAG_KEY_SHORT 0x2
|
|||
|
|
|||
|
//
|
|||
|
// A node of tunneled information in the cache
|
|||
|
//
|
|||
|
// A TUNNEL is allocated in each VCB and initialized at mount time.
|
|||
|
//
|
|||
|
// TUNNEL_NODES are then arranged off of the TUNNEL in a splay tree keyed
|
|||
|
// by DirKey ## Name, where Name is whichever of the names was removed from
|
|||
|
// the directory (short or long). Each node is also timestamped and inserted
|
|||
|
// into a timer queue for age expiration.
|
|||
|
//
|
|||
|
|
|||
|
typedef struct {
|
|||
|
|
|||
|
//
|
|||
|
// Splay links in the Cache tree
|
|||
|
//
|
|||
|
|
|||
|
RTL_SPLAY_LINKS CacheLinks;
|
|||
|
|
|||
|
//
|
|||
|
// List links in the timer queue
|
|||
|
//
|
|||
|
|
|||
|
LIST_ENTRY ListLinks;
|
|||
|
|
|||
|
//
|
|||
|
// Time this entry was created (for constant time insert)
|
|||
|
//
|
|||
|
|
|||
|
LARGE_INTEGER CreateTime;
|
|||
|
|
|||
|
//
|
|||
|
// Directory these names are associated with
|
|||
|
//
|
|||
|
|
|||
|
ULONGLONG DirKey;
|
|||
|
|
|||
|
//
|
|||
|
// Flags for the entry
|
|||
|
//
|
|||
|
|
|||
|
ULONG Flags;
|
|||
|
|
|||
|
//
|
|||
|
// Long/Short names of the file
|
|||
|
//
|
|||
|
|
|||
|
UNICODE_STRING LongName;
|
|||
|
UNICODE_STRING ShortName;
|
|||
|
|
|||
|
//
|
|||
|
// Opaque tunneled data
|
|||
|
//
|
|||
|
|
|||
|
PVOID TunnelData;
|
|||
|
ULONG TunnelDataLength;
|
|||
|
|
|||
|
} TUNNEL_NODE, *PTUNNEL_NODE;
|
|||
|
|
|||
|
//
|
|||
|
// Internal utility functions
|
|||
|
//
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
FsRtlGetTunnelParameterValue (
|
|||
|
IN PUNICODE_STRING ValueName,
|
|||
|
IN OUT PULONG Value);
|
|||
|
|
|||
|
VOID
|
|||
|
FsRtlPruneTunnelCache (
|
|||
|
IN PTUNNEL Cache,
|
|||
|
IN OUT PLIST_ENTRY FreePoolList);
|
|||
|
|
|||
|
#ifdef ALLOC_PRAGMA
|
|||
|
#pragma alloc_text(INIT, FsRtlInitializeTunnels)
|
|||
|
#pragma alloc_text(PAGE, FsRtlInitializeTunnelCache)
|
|||
|
#pragma alloc_text(PAGE, FsRtlAddToTunnelCache)
|
|||
|
#pragma alloc_text(PAGE, FsRtlFindInTunnelCache)
|
|||
|
#pragma alloc_text(PAGE, FsRtlDeleteKeyFromTunnelCache)
|
|||
|
#pragma alloc_text(PAGE, FsRtlDeleteTunnelCache)
|
|||
|
#pragma alloc_text(PAGE, FsRtlPruneTunnelCache)
|
|||
|
#pragma alloc_text(PAGE, FsRtlGetTunnelParameterValue)
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Testing and usermode rig support. Define TUNNELTEST to get verbose debugger
|
|||
|
// output on various operations. Define USERTEST to transform the code into
|
|||
|
// a form which can be compiled in usermode for more efficient debugging.
|
|||
|
//
|
|||
|
|
|||
|
#if defined(TUNNELTEST) || defined(KEYVIEW)
|
|||
|
VOID DumpUnicodeString(UNICODE_STRING *s);
|
|||
|
VOID DumpNode( TUNNEL_NODE *Node, ULONG Indent );
|
|||
|
VOID DumpTunnel( TUNNEL *Tunnel );
|
|||
|
#define DblHex64(a) (ULONG)((a >> 32) & 0xffffffff),(ULONG)(a & 0xffffffff)
|
|||
|
#endif // TUNNELTEST
|
|||
|
|
|||
|
#ifdef USERTEST
|
|||
|
#include <stdio.h>
|
|||
|
#undef KeQuerySystemTime
|
|||
|
#define KeQuerySystemTime NtQuerySystemTime
|
|||
|
#undef ExInitializeFastMutex
|
|||
|
#define ExInitializeFastMutex(arg)
|
|||
|
#define ExAcquireFastMutex(arg)
|
|||
|
#define ExReleaseFastMutex(arg)
|
|||
|
#define DbgPrint printf
|
|||
|
#undef PAGED_CODE
|
|||
|
#define PAGED_CODE()
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
INLINE
|
|||
|
LONG
|
|||
|
FsRtlCompareNodeAndKey (
|
|||
|
TUNNEL_NODE *Node,
|
|||
|
ULONGLONG DirectoryKey,
|
|||
|
PUNICODE_STRING Name
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Compare a tunnel node with a key/name pair
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Node - a tunnel node
|
|||
|
|
|||
|
DirectoryKey - a key value
|
|||
|
|
|||
|
Name - a filename
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Signed comparison result
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
return (Node->DirKey > DirectoryKey ? 1 :
|
|||
|
(Node->DirKey < DirectoryKey ? -1 :
|
|||
|
RtlCompareUnicodeString((FlagOn(Node->Flags, TUNNEL_FLAG_KEY_SHORT) ?
|
|||
|
&Node->ShortName : &Node->LongName),
|
|||
|
Name,
|
|||
|
TRUE)));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
INLINE
|
|||
|
VOID
|
|||
|
FsRtlFreeTunnelNode (
|
|||
|
PTUNNEL_NODE Node,
|
|||
|
PLIST_ENTRY FreePoolList OPTIONAL
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Free a node
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Node - a tunnel node to free
|
|||
|
|
|||
|
FreePoolList - optional list to hold freeable pool memory
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
-*/
|
|||
|
{
|
|||
|
if (FreePoolList) {
|
|||
|
|
|||
|
InsertHeadList(FreePoolList, &Node->ListLinks);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (FlagOn(Node->Flags, TUNNEL_FLAG_NON_LOOKASIDE)) {
|
|||
|
|
|||
|
ExFreePool(Node);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
ExFreeToPagedLookasideList(&TunnelLookasideList, Node);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
INLINE
|
|||
|
VOID
|
|||
|
FsRtlEmptyFreePoolList (
|
|||
|
PLIST_ENTRY FreePoolList
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Free all pool memory that has been delayed onto a free list.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
FreePoolList - a list of freeable pool memory
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
-*/
|
|||
|
{
|
|||
|
PTUNNEL_NODE FreeNode;
|
|||
|
|
|||
|
while (!IsListEmpty(FreePoolList)) {
|
|||
|
|
|||
|
FreeNode = CONTAINING_RECORD(FreePoolList->Flink, TUNNEL_NODE, ListLinks);
|
|||
|
RemoveEntryList(FreePoolList->Flink);
|
|||
|
|
|||
|
FsRtlFreeTunnelNode(FreeNode, NULL);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
INLINE
|
|||
|
VOID
|
|||
|
FsRtlRemoveNodeFromTunnel (
|
|||
|
IN PTUNNEL Cache,
|
|||
|
IN PTUNNEL_NODE Node,
|
|||
|
IN PLIST_ENTRY FreePoolList,
|
|||
|
IN PBOOLEAN Splay OPTIONAL
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Performs the common work of deleting a node from a tunnel cache. Pool memory
|
|||
|
is not deleted immediately but is saved aside on a list for deletion later
|
|||
|
by the calling routine.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Cache - the tunnel cache the node is in
|
|||
|
|
|||
|
Node - the node being removed
|
|||
|
|
|||
|
FreePoolList - an initialized list to take the node if it was allocated from
|
|||
|
pool
|
|||
|
|
|||
|
Splay - an optional flag to indicate whether the tree should be splayed on
|
|||
|
the delete. Set to FALSE if splaying was performed.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
if (Splay && *Splay) {
|
|||
|
|
|||
|
Cache->Cache = RtlDelete(&Node->CacheLinks);
|
|||
|
|
|||
|
*Splay = FALSE;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
RtlDeleteNoSplay(&Node->CacheLinks, &Cache->Cache);
|
|||
|
}
|
|||
|
|
|||
|
RemoveEntryList(&Node->ListLinks);
|
|||
|
|
|||
|
Cache->NumEntries--;
|
|||
|
|
|||
|
FsRtlFreeTunnelNode(Node, FreePoolList);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
FsRtlInitializeTunnels (
|
|||
|
VOID
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Initializes the global part of the tunneling package.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
UNICODE_STRING ValueName;
|
|||
|
USHORT LookasideDepth;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
if (MmIsThisAnNtAsSystem()) {
|
|||
|
|
|||
|
TunnelMaxEntries = 1024;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Query our configurable parameters
|
|||
|
//
|
|||
|
// Don't worry about failure in retrieving from the registry. We've gotten
|
|||
|
// this far so fall back on defaults even if there was a problem with resources.
|
|||
|
//
|
|||
|
|
|||
|
ValueName.Buffer = TUNNEL_SIZE_VALUE_NAME;
|
|||
|
ValueName.Length = sizeof(TUNNEL_SIZE_VALUE_NAME) - sizeof(WCHAR);
|
|||
|
ValueName.MaximumLength = sizeof(TUNNEL_SIZE_VALUE_NAME);
|
|||
|
(VOID) FsRtlGetTunnelParameterValue(&ValueName, &TunnelMaxEntries);
|
|||
|
|
|||
|
ValueName.Buffer = TUNNEL_AGE_VALUE_NAME;
|
|||
|
ValueName.Length = sizeof(TUNNEL_AGE_VALUE_NAME) - sizeof(WCHAR);
|
|||
|
ValueName.MaximumLength = sizeof(TUNNEL_AGE_VALUE_NAME);
|
|||
|
(VOID) FsRtlGetTunnelParameterValue(&ValueName, &TunnelMaxAge);
|
|||
|
|
|||
|
if (TunnelMaxAge == 0) {
|
|||
|
|
|||
|
//
|
|||
|
// If the registry has been set so the timeout is zero, we should force
|
|||
|
// the number of entries to zero also. This preserves expectations and lets
|
|||
|
// us key off of max entries alone in performing the hard disabling of the
|
|||
|
// caching code.
|
|||
|
//
|
|||
|
|
|||
|
TunnelMaxEntries = 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Convert from seconds to 10ths of msecs, the internal resolution
|
|||
|
//
|
|||
|
|
|||
|
TunnelMaxAge *= 10000000;
|
|||
|
|
|||
|
//
|
|||
|
// Build the lookaside list for common node allocation
|
|||
|
//
|
|||
|
|
|||
|
if (TunnelMaxEntries > MAXUSHORT) {
|
|||
|
|
|||
|
//
|
|||
|
// User is hinting a big need to us
|
|||
|
//
|
|||
|
|
|||
|
LookasideDepth = MAX_LOOKASIDE_DEPTH;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
LookasideDepth = ((USHORT)TunnelMaxEntries)/16;
|
|||
|
}
|
|||
|
|
|||
|
if (LookasideDepth == 0 && TunnelMaxEntries) {
|
|||
|
|
|||
|
//
|
|||
|
// Miniscule number of entries allowed. Lookaside 'em all.
|
|||
|
//
|
|||
|
|
|||
|
LookasideDepth = (USHORT)TunnelMaxEntries + 1;
|
|||
|
}
|
|||
|
|
|||
|
if (LookasideDepth > MAX_LOOKASIDE_DEPTH) {
|
|||
|
|
|||
|
//
|
|||
|
// Finally, restrict the depth to something reasonable.
|
|||
|
//
|
|||
|
|
|||
|
LookasideDepth = MAX_LOOKASIDE_DEPTH;
|
|||
|
}
|
|||
|
|
|||
|
ExInitializePagedLookasideList( &TunnelLookasideList,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
0,
|
|||
|
LOOKASIDE_NODE_SIZE,
|
|||
|
'LnuT',
|
|||
|
LookasideDepth );
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// *** SPEC
|
|||
|
//
|
|||
|
// FsRtlInitializeTunnelCache - Initialize a tunneling cache for a volume
|
|||
|
//
|
|||
|
// FsRtlInitializeTunnelCache will allocate a default cache (resizing policy is common
|
|||
|
// to all file systems) and initialize it to be empty. File systems will store a pointer to
|
|||
|
// this cache in their per-volume structures.
|
|||
|
//
|
|||
|
// Information is retained in the tunnel cache for a fixed period of time. MarkZ would
|
|||
|
// assume that a value of 10 seconds would satisfy the vast majority of situations. This
|
|||
|
// could be controlled by the registry or could be a compilation constant.
|
|||
|
//
|
|||
|
// Change: W95 times out at 15 seconds. Would be a registry value initialized at tunnel
|
|||
|
// creation time, with a proposed default of 15 seconds.
|
|||
|
//
|
|||
|
|
|||
|
VOID
|
|||
|
FsRtlInitializeTunnelCache (
|
|||
|
IN PTUNNEL Cache
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Initialize a new tunnel cache.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
ExInitializeFastMutex(&Cache->Mutex);
|
|||
|
|
|||
|
Cache->Cache = NULL;
|
|||
|
InitializeListHead(&Cache->TimerQueue);
|
|||
|
Cache->NumEntries = 0;
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// *** SPEC
|
|||
|
//
|
|||
|
// FsRtlAddToTunnelCache - add information to a tunnel cache
|
|||
|
//
|
|||
|
// FsRtlAddToTunnelCache is called by file systems when a name disappears from a
|
|||
|
// directory. This typically occurs in both the delete and the rename paths. When
|
|||
|
// a name is deleted, all information needed to be cached is extracted from the file
|
|||
|
// and passed in a single buffer. This information is stored keyed by the directory key
|
|||
|
// (a ULONG that is unique to the directory) and the short-name of the file.
|
|||
|
//
|
|||
|
// The caller is required to synchronize this call against FsRtlDeleteTunnelCache.
|
|||
|
//
|
|||
|
// Arguments:
|
|||
|
// Cache pointer to cache initialized by FsRtlInitializeTunnelCache
|
|||
|
// DirectoryKey ULONG unique ID of the directory containing the deleted file
|
|||
|
// ShortName UNICODE_STRING* short (8.3) name of the file
|
|||
|
// LongName UNICODE_STRING* full name of the file
|
|||
|
// DataLength ULONG length of data to be cached with these names
|
|||
|
// Data VOID* data that will be cached.
|
|||
|
//
|
|||
|
// It is acceptable for the Cache to ignore this request based upon memory constraints.
|
|||
|
//
|
|||
|
// Change: W95 maintains 10 items in the tunnel cache. Since we are a potential server
|
|||
|
// this should be much higher. The max count would be initialized from the registry with
|
|||
|
// a proposed default of 1024. Adds which run into the limit would cause least recently
|
|||
|
// inserted recycling (i.e., off of the top of the timer queue).
|
|||
|
//
|
|||
|
// Change: Key should be by the name removed, not neccesarily the short name. If a long name
|
|||
|
// is removed, it would be incorrect to miss the tunnel. Use KeyByShortName boolean to specify
|
|||
|
// which.
|
|||
|
//
|
|||
|
// Change: Specify that Data, ShortName, and LongName are copied for storage.
|
|||
|
//
|
|||
|
|
|||
|
VOID
|
|||
|
FsRtlAddToTunnelCache (
|
|||
|
IN PTUNNEL Cache,
|
|||
|
IN ULONGLONG DirKey,
|
|||
|
IN PUNICODE_STRING ShortName,
|
|||
|
IN PUNICODE_STRING LongName,
|
|||
|
IN BOOLEAN KeyByShortName,
|
|||
|
IN ULONG DataLength,
|
|||
|
IN PVOID Data
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Adds an entry to the tunnel cache keyed by
|
|||
|
|
|||
|
DirectoryKey ## (KeyByShortName ? ShortName : LongName)
|
|||
|
|
|||
|
ShortName, LongName, and Data are copied and stored in the tunnel. As a side
|
|||
|
effect, if there are too many entries in the tunnel cache, this routine will
|
|||
|
initiate expiration in the tunnel cache.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Cache - a tunnel cache initialized by FsRtlInitializeTunnelCache()
|
|||
|
|
|||
|
DirKey - the key value of the directory the name appeared in
|
|||
|
|
|||
|
ShortName - (optional if !KeyByShortName) the 8.3 name of the file
|
|||
|
|
|||
|
LongName - (optional if KeyByShortName) the long name of the file
|
|||
|
|
|||
|
KeyByShortName - specifies which name is keyed in the tunnel cache
|
|||
|
|
|||
|
DataLength - specifies the length of the opaque data segment (file
|
|||
|
system specific) which contains the tunnelling information for this
|
|||
|
file
|
|||
|
|
|||
|
Data - pointer to the opaque tunneling data segment
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
LONG Compare;
|
|||
|
ULONG NodeSize;
|
|||
|
PUNICODE_STRING NameKey;
|
|||
|
PRTL_SPLAY_LINKS *Links;
|
|||
|
LIST_ENTRY FreePoolList;
|
|||
|
|
|||
|
PTUNNEL_NODE Node = NULL;
|
|||
|
PTUNNEL_NODE NewNode = NULL;
|
|||
|
BOOLEAN FreeOldNode = FALSE;
|
|||
|
BOOLEAN AllocatedFromPool = FALSE;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// If MaxEntries is 0 then tunneling is disabled.
|
|||
|
//
|
|||
|
|
|||
|
if (TunnelMaxEntries == 0) return;
|
|||
|
|
|||
|
InitializeListHead(&FreePoolList);
|
|||
|
|
|||
|
//
|
|||
|
// Grab a new node for this data
|
|||
|
//
|
|||
|
|
|||
|
NodeSize = sizeof(TUNNEL_NODE) + ShortName->Length + LongName->Length + DataLength;
|
|||
|
|
|||
|
if (LOOKASIDE_NODE_SIZE >= NodeSize) {
|
|||
|
|
|||
|
NewNode = ExAllocateFromPagedLookasideList(&TunnelLookasideList);
|
|||
|
}
|
|||
|
|
|||
|
if (NewNode == NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// Data doesn't fit in lookaside nodes
|
|||
|
//
|
|||
|
|
|||
|
NewNode = ExAllocatePoolWithTag(PagedPool|POOL_COLD_ALLOCATION, NodeSize, 'PnuT');
|
|||
|
|
|||
|
if (NewNode == NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// Give up tunneling this entry
|
|||
|
//
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
AllocatedFromPool = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Traverse the cache to find our insertion point
|
|||
|
//
|
|||
|
|
|||
|
NameKey = (KeyByShortName ? ShortName : LongName);
|
|||
|
|
|||
|
ExAcquireFastMutex(&Cache->Mutex);
|
|||
|
|
|||
|
Links = &Cache->Cache;
|
|||
|
|
|||
|
while (*Links) {
|
|||
|
|
|||
|
Node = CONTAINING_RECORD(*Links, TUNNEL_NODE, CacheLinks);
|
|||
|
|
|||
|
Compare = FsRtlCompareNodeAndKey(Node, DirKey, NameKey);
|
|||
|
|
|||
|
if (Compare > 0) {
|
|||
|
|
|||
|
Links = &RtlLeftChild(&Node->CacheLinks);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (Compare < 0) {
|
|||
|
|
|||
|
Links = &RtlRightChild(&Node->CacheLinks);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Thread new data into the splay tree
|
|||
|
//
|
|||
|
|
|||
|
RtlInitializeSplayLinks(&NewNode->CacheLinks);
|
|||
|
|
|||
|
if (Node) {
|
|||
|
|
|||
|
//
|
|||
|
// Not inserting first node in tree
|
|||
|
//
|
|||
|
|
|||
|
if (*Links) {
|
|||
|
|
|||
|
//
|
|||
|
// Entry exists in the cache, so replace by swapping all splay links
|
|||
|
//
|
|||
|
|
|||
|
RtlRightChild(&NewNode->CacheLinks) = RtlRightChild(*Links);
|
|||
|
RtlLeftChild(&NewNode->CacheLinks) = RtlLeftChild(*Links);
|
|||
|
|
|||
|
if (RtlRightChild(*Links)) RtlParent(RtlRightChild(*Links)) = &NewNode->CacheLinks;
|
|||
|
if (RtlLeftChild(*Links)) RtlParent(RtlLeftChild(*Links)) = &NewNode->CacheLinks;
|
|||
|
|
|||
|
if (!RtlIsRoot(*Links)) {
|
|||
|
|
|||
|
//
|
|||
|
// Change over the parent links. Note that we've messed with *Links now
|
|||
|
// since it is pointing at the parent member.
|
|||
|
//
|
|||
|
|
|||
|
RtlParent(&NewNode->CacheLinks) = RtlParent(*Links);
|
|||
|
|
|||
|
if (RtlIsLeftChild(*Links)) {
|
|||
|
|
|||
|
RtlLeftChild(RtlParent(*Links)) = &NewNode->CacheLinks;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
RtlRightChild(RtlParent(*Links)) = &NewNode->CacheLinks;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Set root of the cache
|
|||
|
//
|
|||
|
|
|||
|
Cache->Cache = &NewNode->CacheLinks;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Free old node
|
|||
|
//
|
|||
|
|
|||
|
RemoveEntryList(&Node->ListLinks);
|
|||
|
|
|||
|
FsRtlFreeTunnelNode(Node, &FreePoolList);
|
|||
|
|
|||
|
Cache->NumEntries--;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Simple insertion as a leaf
|
|||
|
//
|
|||
|
|
|||
|
NewNode->CacheLinks.Parent = &Node->CacheLinks;
|
|||
|
*Links = &NewNode->CacheLinks;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
Cache->Cache = &NewNode->CacheLinks;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Thread onto the timer list
|
|||
|
//
|
|||
|
|
|||
|
KeQuerySystemTime(&NewNode->CreateTime);
|
|||
|
InsertTailList(&Cache->TimerQueue, &NewNode->ListLinks);
|
|||
|
|
|||
|
Cache->NumEntries++;
|
|||
|
|
|||
|
//
|
|||
|
// Stash tunneling information
|
|||
|
//
|
|||
|
|
|||
|
NewNode->DirKey = DirKey;
|
|||
|
|
|||
|
if (KeyByShortName) {
|
|||
|
|
|||
|
NewNode->Flags = TUNNEL_FLAG_KEY_SHORT;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
NewNode->Flags = 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the internal UNICODE_STRINGS to point at the buffer segments. For various
|
|||
|
// reasons (UNICODE APIs are incomplete, we're avoiding calling any allocate routine more
|
|||
|
// than once, UNICODE strings are not guaranteed to be null terminated) we have to do a lot
|
|||
|
// of this by hand.
|
|||
|
//
|
|||
|
// The data is layed out like this in the allocated block:
|
|||
|
//
|
|||
|
// -----------------------------------------------------------------------------------
|
|||
|
// | TUNNEL_NODE | Node->ShortName.Buffer | Node->LongName.Buffer | Node->TunnelData |
|
|||
|
// -----------------------------------------------------------------------------------
|
|||
|
//
|
|||
|
|
|||
|
NewNode->ShortName.Buffer = (PWCHAR)((PCHAR)NewNode + sizeof(TUNNEL_NODE));
|
|||
|
NewNode->LongName.Buffer = (PWCHAR)((PCHAR)NewNode + sizeof(TUNNEL_NODE) + ShortName->Length);
|
|||
|
|
|||
|
NewNode->ShortName.Length = NewNode->ShortName.MaximumLength = ShortName->Length;
|
|||
|
NewNode->LongName.Length = NewNode->LongName.MaximumLength = LongName->Length;
|
|||
|
|
|||
|
if (ShortName->Length) {
|
|||
|
|
|||
|
RtlCopyMemory(NewNode->ShortName.Buffer, ShortName->Buffer, ShortName->Length);
|
|||
|
}
|
|||
|
|
|||
|
if (LongName->Length) {
|
|||
|
|
|||
|
RtlCopyMemory(NewNode->LongName.Buffer, LongName->Buffer, LongName->Length);
|
|||
|
}
|
|||
|
|
|||
|
NewNode->TunnelData = (PVOID)((PCHAR)NewNode + sizeof(TUNNEL_NODE) + ShortName->Length + LongName->Length);
|
|||
|
|
|||
|
NewNode->TunnelDataLength = DataLength;
|
|||
|
|
|||
|
RtlCopyMemory(NewNode->TunnelData, Data, DataLength);
|
|||
|
|
|||
|
if (AllocatedFromPool) {
|
|||
|
|
|||
|
SetFlag(NewNode->Flags, TUNNEL_FLAG_NON_LOOKASIDE);
|
|||
|
}
|
|||
|
|
|||
|
#if defined(TUNNELTEST) || defined (KEYVIEW)
|
|||
|
DbgPrint("FsRtlAddToTunnelCache:\n");
|
|||
|
DumpNode(NewNode, 1);
|
|||
|
#ifndef KEYVIEW
|
|||
|
DumpTunnel(Cache);
|
|||
|
#endif
|
|||
|
#endif // TUNNELTEST
|
|||
|
|
|||
|
//
|
|||
|
// Clean out the cache, release, and then drop any pool memory we need to
|
|||
|
//
|
|||
|
|
|||
|
FsRtlPruneTunnelCache(Cache, &FreePoolList);
|
|||
|
|
|||
|
ExReleaseFastMutex(&Cache->Mutex);
|
|||
|
|
|||
|
FsRtlEmptyFreePoolList(&FreePoolList);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// *** SPEC
|
|||
|
//
|
|||
|
// FsRtlFindInTunnelCache - retrieve information from tunnel cache
|
|||
|
//
|
|||
|
// FsRtlFindInTunnelCache consults the cache to see if an entry with the same
|
|||
|
// DirectoryKey and ShortName exist. If so, it returns the data associated with the
|
|||
|
// cache entry. The entry may or may not be freed from the cache. Information that is
|
|||
|
// stale but not yet purged (older than the retention threshold but not yet cleaned out)
|
|||
|
// may be returned.
|
|||
|
//
|
|||
|
// File systems call FsRtlFindInTunnel cache in the create path when a new file is
|
|||
|
// being created and in the rename path when a new name is appearing in a directory.
|
|||
|
//
|
|||
|
// The caller is required to synchronize this call against FsRtlDeleteTunnelCache.
|
|||
|
//
|
|||
|
// Arguments:
|
|||
|
// Cache a tunnel cache initialized by FsRtlInitializeTunnelCache()
|
|||
|
// DirectoryKey ULONG unique ID of the directory where a name is appearing
|
|||
|
// Name UNICODE_STRING* name that is being created
|
|||
|
// DataLength in length of buffer, out returned length of data found
|
|||
|
// Data pointer to buffer
|
|||
|
//
|
|||
|
// Returns:
|
|||
|
// TRUE iff a matching DirectoryKey/Name pair are found, FALSE otherwise
|
|||
|
//
|
|||
|
// Change: Add out parameters ShortName and LongName to capture the file naming information.
|
|||
|
// Plus: this avoids the need for marshalling/unmarshalling steps for the current desired use of
|
|||
|
// this code since otherwise we'd have variable length unaligned structures to contain the
|
|||
|
// strings along with the other meta-info.
|
|||
|
// Minus: Possibly a bad precedent.
|
|||
|
//
|
|||
|
// Change: spec reads "may or may not be freed from cache" on a hit. This complicates unwinding
|
|||
|
// from aborted operations. Data will not be freed on a hit, but will expire like normal entries.
|
|||
|
//
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
FsRtlFindInTunnelCache (
|
|||
|
IN PTUNNEL Cache,
|
|||
|
IN ULONGLONG DirKey,
|
|||
|
IN PUNICODE_STRING Name,
|
|||
|
OUT PUNICODE_STRING ShortName,
|
|||
|
OUT PUNICODE_STRING LongName,
|
|||
|
IN OUT PULONG DataLength,
|
|||
|
OUT PVOID Data
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Looks up the key
|
|||
|
|
|||
|
DirKey ## Name
|
|||
|
|
|||
|
in the tunnel cache and removes it. As a side effect, this routine will initiate
|
|||
|
expiration of the aged entries in the tunnel cache.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Cache - a tunnel cache initialized by FsRtlInitializeTunnelCache()
|
|||
|
|
|||
|
DirKey - the key value of the directory the name will appear in
|
|||
|
|
|||
|
Name - the name of the entry
|
|||
|
|
|||
|
ShortName - return string to hold the short name of the tunneled file. Must
|
|||
|
already be allocated and large enough for max 8.3 name
|
|||
|
|
|||
|
LongName - return string to hold the long name of the tunneled file. If
|
|||
|
already allocated, may be grown if not large enough. Caller is
|
|||
|
responsible for noticing this and freeing data regardless of return value.
|
|||
|
|
|||
|
DataLength - provides the length of the buffer avaliable to hold the
|
|||
|
tunneling information, returns the size of the tunneled information
|
|||
|
read out
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Boolean true if found, false otherwise
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PRTL_SPLAY_LINKS Links;
|
|||
|
PTUNNEL_NODE Node;
|
|||
|
LONG Compare;
|
|||
|
LIST_ENTRY FreePoolList;
|
|||
|
|
|||
|
BOOLEAN Status = FALSE;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// If MaxEntries is 0 then tunneling is disabled.
|
|||
|
//
|
|||
|
|
|||
|
if (TunnelMaxEntries == 0) return FALSE;
|
|||
|
|
|||
|
InitializeListHead(&FreePoolList);
|
|||
|
|
|||
|
#ifdef KEYVIEW
|
|||
|
DbgPrint("++\nSearching for %wZ , %08x%08x\n--\n", Name, DblHex64(DirKey));
|
|||
|
#endif
|
|||
|
|
|||
|
ExAcquireFastMutex(&Cache->Mutex);
|
|||
|
|
|||
|
//
|
|||
|
// Expire aged entries first so we don't grab old data
|
|||
|
//
|
|||
|
|
|||
|
FsRtlPruneTunnelCache(Cache, &FreePoolList);
|
|||
|
|
|||
|
Links = Cache->Cache;
|
|||
|
|
|||
|
while (Links) {
|
|||
|
|
|||
|
Node = CONTAINING_RECORD(Links, TUNNEL_NODE, CacheLinks);
|
|||
|
|
|||
|
Compare = FsRtlCompareNodeAndKey(Node, DirKey, Name);
|
|||
|
|
|||
|
if (Compare > 0) {
|
|||
|
|
|||
|
Links = RtlLeftChild(&Node->CacheLinks);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (Compare < 0) {
|
|||
|
|
|||
|
Links = RtlRightChild(&Node->CacheLinks);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Found tunneling information
|
|||
|
//
|
|||
|
|
|||
|
#if defined(TUNNELTEST) || defined(KEYVIEW)
|
|||
|
DbgPrint("FsRtlFindInTunnelCache:\n");
|
|||
|
DumpNode(Node, 1);
|
|||
|
#ifndef KEYVIEW
|
|||
|
DumpTunnel(Cache);
|
|||
|
#endif
|
|||
|
#endif // TUNNELTEST
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
if (Links) {
|
|||
|
|
|||
|
//
|
|||
|
// Copy node data into caller's area
|
|||
|
//
|
|||
|
|
|||
|
ASSERT(ShortName->MaximumLength >= (8+1+3)*sizeof(WCHAR));
|
|||
|
RtlCopyUnicodeString(ShortName, &Node->ShortName);
|
|||
|
|
|||
|
if (LongName->MaximumLength >= Node->LongName.Length) {
|
|||
|
|
|||
|
RtlCopyUnicodeString(LongName, &Node->LongName);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Need to allocate more memory for the long name
|
|||
|
//
|
|||
|
|
|||
|
LongName->Buffer = FsRtlAllocatePoolWithTag(PagedPool, Node->LongName.Length, '4nuT');
|
|||
|
LongName->Length = LongName->MaximumLength = Node->LongName.Length;
|
|||
|
|
|||
|
RtlCopyMemory(LongName->Buffer, Node->LongName.Buffer, Node->LongName.Length);
|
|||
|
}
|
|||
|
|
|||
|
ASSERT(*DataLength >= Node->TunnelDataLength);
|
|||
|
RtlCopyMemory(Data, Node->TunnelData, Node->TunnelDataLength);
|
|||
|
*DataLength = Node->TunnelDataLength;
|
|||
|
|
|||
|
Status = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
} finally {
|
|||
|
|
|||
|
ExReleaseFastMutex(&Cache->Mutex);
|
|||
|
|
|||
|
FsRtlEmptyFreePoolList(&FreePoolList);
|
|||
|
}
|
|||
|
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// *** SPEC
|
|||
|
//
|
|||
|
// FsRtlDeleteKeyFromTunnelCache - delete all cached information associated with
|
|||
|
// a DirectoryKey
|
|||
|
//
|
|||
|
// When file systems delete a directory, all cached information relating to that directory
|
|||
|
// must be purged. File systems call FsRtlDeleteKeyFromTunnelCache in the rmdir path.
|
|||
|
//
|
|||
|
// The caller is required to synchronize this call against FsRtlDeleteTunnelCache.
|
|||
|
//
|
|||
|
// Arguments:
|
|||
|
// Cache a tunnel cache initialized by FsRtlInitializeTunnelCache()
|
|||
|
// DirectoryKey ULONGLONG unique ID of the directory that is being deleted
|
|||
|
//
|
|||
|
|
|||
|
VOID
|
|||
|
FsRtlDeleteKeyFromTunnelCache (
|
|||
|
IN PTUNNEL Cache,
|
|||
|
IN ULONGLONG DirKey
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Deletes all entries in the cache associated with a specific directory
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Cache - a tunnel cache initialized by FsRtlInitializeTunnelCache()
|
|||
|
|
|||
|
DirKey - the key value of the directory (presumeably being removed)
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PRTL_SPLAY_LINKS Links;
|
|||
|
PRTL_SPLAY_LINKS SuccessorLinks;
|
|||
|
PTUNNEL_NODE Node;
|
|||
|
LIST_ENTRY FreePoolList;
|
|||
|
|
|||
|
PRTL_SPLAY_LINKS LastLinks = NULL;
|
|||
|
BOOLEAN Splay = TRUE;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// If MaxEntries is 0 then tunneling is disabled.
|
|||
|
//
|
|||
|
|
|||
|
if (TunnelMaxEntries == 0) return;
|
|||
|
|
|||
|
InitializeListHead(&FreePoolList);
|
|||
|
|
|||
|
#ifdef KEYVIEW
|
|||
|
DbgPrint("++\nDeleting key %08x%08x\n--\n", DblHex64(DirKey));
|
|||
|
#endif
|
|||
|
|
|||
|
ExAcquireFastMutex(&Cache->Mutex);
|
|||
|
|
|||
|
Links = Cache->Cache;
|
|||
|
|
|||
|
while (Links) {
|
|||
|
|
|||
|
Node = CONTAINING_RECORD(Links, TUNNEL_NODE, CacheLinks);
|
|||
|
|
|||
|
if (Node->DirKey > DirKey) {
|
|||
|
|
|||
|
//
|
|||
|
// All nodes to the right are bigger, go left
|
|||
|
//
|
|||
|
|
|||
|
Links = RtlLeftChild(&Node->CacheLinks);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (Node->DirKey < DirKey) {
|
|||
|
|
|||
|
if (LastLinks) {
|
|||
|
|
|||
|
//
|
|||
|
// If we have previously seen a candidate node to delete
|
|||
|
// and we have now gone too far left - we know where to start.
|
|||
|
//
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
Links = RtlRightChild(&Node->CacheLinks);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Node is a candidate to be deleted, but we might have more nodes
|
|||
|
// to the left in the tree. Note this location and go on.
|
|||
|
//
|
|||
|
|
|||
|
LastLinks = Links;
|
|||
|
Links = RtlLeftChild(&Node->CacheLinks);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
for (Links = LastLinks;
|
|||
|
Links;
|
|||
|
Links = SuccessorLinks) {
|
|||
|
|
|||
|
SuccessorLinks = RtlRealSuccessor(Links);
|
|||
|
Node = CONTAINING_RECORD(Links, TUNNEL_NODE, CacheLinks);
|
|||
|
|
|||
|
if (Node->DirKey != DirKey) {
|
|||
|
|
|||
|
//
|
|||
|
// Reached nodes which have a different key, so we're done
|
|||
|
//
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
FsRtlRemoveNodeFromTunnel(Cache, Node, &FreePoolList, &Splay);
|
|||
|
}
|
|||
|
|
|||
|
#ifdef TUNNELTEST
|
|||
|
DbgPrint("FsRtlDeleteKeyFromTunnelCache:\n");
|
|||
|
#ifndef KEYVIEW
|
|||
|
DumpTunnel(Cache);
|
|||
|
#endif
|
|||
|
#endif // TUNNELTEST
|
|||
|
|
|||
|
ExReleaseFastMutex(&Cache->Mutex);
|
|||
|
|
|||
|
//
|
|||
|
// Free delayed pool
|
|||
|
//
|
|||
|
|
|||
|
FsRtlEmptyFreePoolList(&FreePoolList);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// *** SPEC
|
|||
|
//
|
|||
|
// FsRtlDeleteTunnelCache - free a tunnel cache
|
|||
|
//
|
|||
|
// FsRtlDeleteTunnelCache deletes all cached information. The Cache is no longer
|
|||
|
// valid.
|
|||
|
//
|
|||
|
// Arguments:
|
|||
|
// Cache a tunnel cache initialized by FsRtlInitializeTunnelCache()
|
|||
|
//
|
|||
|
|
|||
|
VOID
|
|||
|
FsRtlDeleteTunnelCache (
|
|||
|
IN PTUNNEL Cache
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Deletes a tunnel cache
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Cache - the cache to delete, initialized by FsRtlInitializeTunnelCache()
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PTUNNEL_NODE Node;
|
|||
|
PLIST_ENTRY Link, Next;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// If MaxEntries is 0 then tunneling is disabled.
|
|||
|
//
|
|||
|
|
|||
|
if (TunnelMaxEntries == 0) return;
|
|||
|
|
|||
|
//
|
|||
|
// Zero out the cache and delete everything on the timer list
|
|||
|
//
|
|||
|
|
|||
|
Cache->Cache = NULL;
|
|||
|
Cache->NumEntries = 0;
|
|||
|
|
|||
|
for (Link = Cache->TimerQueue.Flink;
|
|||
|
Link != &Cache->TimerQueue;
|
|||
|
Link = Next) {
|
|||
|
|
|||
|
Next = Link->Flink;
|
|||
|
|
|||
|
Node = CONTAINING_RECORD(Link, TUNNEL_NODE, ListLinks);
|
|||
|
|
|||
|
FsRtlFreeTunnelNode(Node, NULL);
|
|||
|
}
|
|||
|
|
|||
|
InitializeListHead(&Cache->TimerQueue);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
FsRtlPruneTunnelCache (
|
|||
|
IN PTUNNEL Cache,
|
|||
|
IN OUT PLIST_ENTRY FreePoolList
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Removes deadwood entries from the tunnel cache as defined by TunnelMaxAge and TunnelMaxEntries.
|
|||
|
Pool memory is returned on a list for deletion by the calling routine at a time of
|
|||
|
its choosing.
|
|||
|
|
|||
|
For performance reasons we don't want to force freeing of memory inside a mutex.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Cache - the tunnel cache to prune
|
|||
|
|
|||
|
FreePoolList - a list to queue pool memory on to
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
--*/
|
|||
|
{
|
|||
|
PTUNNEL_NODE Node;
|
|||
|
LARGE_INTEGER ExpireTime;
|
|||
|
LARGE_INTEGER CurrentTime;
|
|||
|
BOOLEAN Splay = TRUE;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// Calculate the age of the oldest entry we want to keep
|
|||
|
//
|
|||
|
|
|||
|
KeQuerySystemTime(&CurrentTime);
|
|||
|
ExpireTime.QuadPart = CurrentTime.QuadPart - TunnelMaxAge;
|
|||
|
|
|||
|
//
|
|||
|
// Expire old entries off of the timer queue. We have to check
|
|||
|
// for future time because the clock may jump as a result of
|
|||
|
// hard clock change. If we did not do this, a rogue entry
|
|||
|
// with a future time could sit at the top of the queue and
|
|||
|
// prevent entries from going away.
|
|||
|
//
|
|||
|
|
|||
|
while (!IsListEmpty(&Cache->TimerQueue)) {
|
|||
|
|
|||
|
Node = CONTAINING_RECORD(Cache->TimerQueue.Flink, TUNNEL_NODE, ListLinks);
|
|||
|
|
|||
|
if (Node->CreateTime.QuadPart < ExpireTime.QuadPart ||
|
|||
|
Node->CreateTime.QuadPart > CurrentTime.QuadPart) {
|
|||
|
|
|||
|
#if defined(TUNNELTEST) || defined(KEYVIEW)
|
|||
|
DbgPrint("Expiring node %x (%ud%ud 1/10 msec too old)\n", Node, DblHex64(ExpireTime.QuadPart - Node->CreateTime.QuadPart));
|
|||
|
#endif // TUNNELTEST
|
|||
|
|
|||
|
FsRtlRemoveNodeFromTunnel(Cache, Node, FreePoolList, &Splay);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// No more nodes to be expired
|
|||
|
//
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Remove entries until we're under the TunnelMaxEntries limit
|
|||
|
//
|
|||
|
|
|||
|
while (Cache->NumEntries > TunnelMaxEntries) {
|
|||
|
|
|||
|
Node = CONTAINING_RECORD(Cache->TimerQueue.Flink, TUNNEL_NODE, ListLinks);
|
|||
|
|
|||
|
#if defined(TUNNELTEST) || defined(KEYVIEW)
|
|||
|
DbgPrint("Dumping node %x (%d > %d)\n", Node, Cache->NumEntries, TunnelMaxEntries);
|
|||
|
#endif // TUNNELTEST
|
|||
|
|
|||
|
FsRtlRemoveNodeFromTunnel(Cache, Node, FreePoolList, &Splay);
|
|||
|
}
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
FsRtlGetTunnelParameterValue (
|
|||
|
IN PUNICODE_STRING ValueName,
|
|||
|
IN OUT PULONG Value
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Given a unicode value name this routine will go into the registry
|
|||
|
location for the Tunnel parameter information and get the
|
|||
|
value.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ValueName - the unicode name for the registry value located in the
|
|||
|
double space configuration location of the registry.
|
|||
|
Value - a pointer to the ULONG for the result.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
|
|||
|
If STATUS_SUCCESSFUL is returned, the location *Value will be
|
|||
|
updated with the DWORD value from the registry. If any failing
|
|||
|
status is returned, this value is untouched.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
HANDLE Handle;
|
|||
|
NTSTATUS Status;
|
|||
|
ULONG RequestLength;
|
|||
|
ULONG ResultLength;
|
|||
|
UCHAR Buffer[KEY_WORK_AREA];
|
|||
|
UNICODE_STRING KeyName;
|
|||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|||
|
PKEY_VALUE_FULL_INFORMATION KeyValueInformation;
|
|||
|
|
|||
|
KeyName.Buffer = TUNNEL_KEY_NAME;
|
|||
|
KeyName.Length = sizeof(TUNNEL_KEY_NAME) - sizeof(WCHAR);
|
|||
|
KeyName.MaximumLength = sizeof(TUNNEL_KEY_NAME);
|
|||
|
|
|||
|
InitializeObjectAttributes(&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
NULL,
|
|||
|
NULL);
|
|||
|
|
|||
|
Status = ZwOpenKey(&Handle,
|
|||
|
KEY_READ,
|
|||
|
&ObjectAttributes);
|
|||
|
|
|||
|
if (!NT_SUCCESS(Status)) {
|
|||
|
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
RequestLength = KEY_WORK_AREA;
|
|||
|
|
|||
|
KeyValueInformation = (PKEY_VALUE_FULL_INFORMATION)Buffer;
|
|||
|
|
|||
|
while (1) {
|
|||
|
|
|||
|
Status = ZwQueryValueKey(Handle,
|
|||
|
ValueName,
|
|||
|
KeyValueFullInformation,
|
|||
|
KeyValueInformation,
|
|||
|
RequestLength,
|
|||
|
&ResultLength);
|
|||
|
|
|||
|
ASSERT( Status != STATUS_BUFFER_OVERFLOW );
|
|||
|
|
|||
|
if (Status == STATUS_BUFFER_OVERFLOW) {
|
|||
|
|
|||
|
//
|
|||
|
// Try to get a buffer big enough.
|
|||
|
//
|
|||
|
|
|||
|
if (KeyValueInformation != (PKEY_VALUE_FULL_INFORMATION)Buffer) {
|
|||
|
|
|||
|
ExFreePool(KeyValueInformation);
|
|||
|
}
|
|||
|
|
|||
|
RequestLength += 256;
|
|||
|
|
|||
|
KeyValueInformation = (PKEY_VALUE_FULL_INFORMATION)
|
|||
|
ExAllocatePoolWithTag(PagedPool,
|
|||
|
RequestLength,
|
|||
|
'KnuT');
|
|||
|
|
|||
|
if (!KeyValueInformation) {
|
|||
|
return STATUS_NO_MEMORY;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
ZwClose(Handle);
|
|||
|
|
|||
|
if (NT_SUCCESS(Status)) {
|
|||
|
|
|||
|
if (KeyValueInformation->DataLength != 0) {
|
|||
|
|
|||
|
PULONG DataPtr;
|
|||
|
|
|||
|
//
|
|||
|
// Return contents to the caller.
|
|||
|
//
|
|||
|
|
|||
|
DataPtr = (PULONG)
|
|||
|
((PUCHAR)KeyValueInformation + KeyValueInformation->DataOffset);
|
|||
|
*Value = *DataPtr;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Treat as if no value was found
|
|||
|
//
|
|||
|
|
|||
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (KeyValueInformation != (PKEY_VALUE_FULL_INFORMATION)Buffer) {
|
|||
|
|
|||
|
ExFreePool(KeyValueInformation);
|
|||
|
}
|
|||
|
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#if defined(TUNNELTEST) || defined(KEYVIEW)
|
|||
|
|
|||
|
VOID
|
|||
|
DumpTunnel (
|
|||
|
PTUNNEL Tunnel
|
|||
|
)
|
|||
|
{
|
|||
|
PRTL_SPLAY_LINKS SplayLinks, Ptr;
|
|||
|
PTUNNEL_NODE Node;
|
|||
|
PLIST_ENTRY Link;
|
|||
|
ULONG Indent = 1, i;
|
|||
|
ULONG EntryCount = 0;
|
|||
|
BOOLEAN CountOff = FALSE;
|
|||
|
|
|||
|
DbgPrint("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
|
|||
|
|
|||
|
DbgPrint("NumEntries = %d\n", Tunnel->NumEntries);
|
|||
|
DbgPrint("****** Cache Tree\n");
|
|||
|
|
|||
|
SplayLinks = Tunnel->Cache;
|
|||
|
|
|||
|
if (SplayLinks == NULL) {
|
|||
|
|
|||
|
goto end;
|
|||
|
}
|
|||
|
|
|||
|
while (RtlLeftChild(SplayLinks) != NULL) {
|
|||
|
|
|||
|
SplayLinks = RtlLeftChild(SplayLinks);
|
|||
|
Indent++;
|
|||
|
}
|
|||
|
|
|||
|
while (SplayLinks) {
|
|||
|
|
|||
|
Node = CONTAINING_RECORD( SplayLinks, TUNNEL_NODE, CacheLinks );
|
|||
|
|
|||
|
EntryCount++;
|
|||
|
|
|||
|
DumpNode(Node, Indent);
|
|||
|
|
|||
|
Ptr = SplayLinks;
|
|||
|
|
|||
|
/*
|
|||
|
first check to see if there is a right subtree to the input link
|
|||
|
if there is then the real successor is the left most node in
|
|||
|
the right subtree. That is find and return P in the following diagram
|
|||
|
|
|||
|
Links
|
|||
|
\
|
|||
|
.
|
|||
|
.
|
|||
|
.
|
|||
|
/
|
|||
|
P
|
|||
|
\
|
|||
|
*/
|
|||
|
|
|||
|
if ((Ptr = RtlRightChild(SplayLinks)) != NULL) {
|
|||
|
|
|||
|
Indent++;
|
|||
|
while (RtlLeftChild(Ptr) != NULL) {
|
|||
|
|
|||
|
Indent++;
|
|||
|
Ptr = RtlLeftChild(Ptr);
|
|||
|
}
|
|||
|
|
|||
|
SplayLinks = Ptr;
|
|||
|
|
|||
|
} else {
|
|||
|
/*
|
|||
|
we do not have a right child so check to see if have a parent and if
|
|||
|
so find the first ancestor that we are a left decendent of. That
|
|||
|
is find and return P in the following diagram
|
|||
|
|
|||
|
P
|
|||
|
/
|
|||
|
.
|
|||
|
.
|
|||
|
.
|
|||
|
Links
|
|||
|
*/
|
|||
|
|
|||
|
Ptr = SplayLinks;
|
|||
|
while (RtlIsRightChild(Ptr)) {
|
|||
|
|
|||
|
Indent--;
|
|||
|
Ptr = RtlParent(Ptr);
|
|||
|
}
|
|||
|
|
|||
|
if (!RtlIsLeftChild(Ptr)) {
|
|||
|
|
|||
|
//
|
|||
|
// we do not have a real successor so we simply return
|
|||
|
// NULL
|
|||
|
//
|
|||
|
SplayLinks = NULL;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
Indent--;
|
|||
|
SplayLinks = RtlParent(Ptr);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
end:
|
|||
|
|
|||
|
if (CountOff = (EntryCount != Tunnel->NumEntries)) {
|
|||
|
|
|||
|
DbgPrint("!!!!!!!!!! Splay Tree Count Mismatch (%d != %d)\n", EntryCount, Tunnel->NumEntries);
|
|||
|
}
|
|||
|
|
|||
|
EntryCount = 0;
|
|||
|
|
|||
|
DbgPrint("****** Timer Queue\n");
|
|||
|
|
|||
|
for (Link = Tunnel->TimerQueue.Flink;
|
|||
|
Link != &Tunnel->TimerQueue;
|
|||
|
Link = Link->Flink) {
|
|||
|
|
|||
|
Node = CONTAINING_RECORD( Link, TUNNEL_NODE, ListLinks );
|
|||
|
|
|||
|
EntryCount++;
|
|||
|
|
|||
|
DumpNode(Node, 1);
|
|||
|
}
|
|||
|
|
|||
|
if (CountOff |= (EntryCount != Tunnel->NumEntries)) {
|
|||
|
|
|||
|
DbgPrint("!!!!!!!!!! Timer Queue Count Mismatch (%d != %d)\n", EntryCount, Tunnel->NumEntries);
|
|||
|
}
|
|||
|
|
|||
|
ASSERT(!CountOff);
|
|||
|
|
|||
|
DbgPrint("------------------------------------------------------------------\n");
|
|||
|
}
|
|||
|
|
|||
|
#define MAXINDENT 128
|
|||
|
#define INDENTSTEP 3
|
|||
|
|
|||
|
VOID
|
|||
|
DumpNode (
|
|||
|
PTUNNEL_NODE Node,
|
|||
|
ULONG Indent
|
|||
|
)
|
|||
|
{
|
|||
|
ULONG i;
|
|||
|
CHAR SpaceBuf[MAXINDENT*INDENTSTEP + 1];
|
|||
|
|
|||
|
Indent--;
|
|||
|
if (Indent > MAXINDENT) {
|
|||
|
Indent = MAXINDENT;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// DbgPrint is really expensive to iteratively call to do the indenting,
|
|||
|
// so just build up the indentation all at once on the stack.
|
|||
|
//
|
|||
|
|
|||
|
RtlFillMemory(SpaceBuf, Indent*INDENTSTEP, ' ');
|
|||
|
SpaceBuf[Indent*INDENTSTEP] = '\0';
|
|||
|
|
|||
|
DbgPrint("%sNode 0x%x CreateTime = %08x%08x, DirKey = %08x%08x, Flags = %d\n",
|
|||
|
SpaceBuf,
|
|||
|
Node,
|
|||
|
DblHex64(Node->CreateTime.QuadPart),
|
|||
|
DblHex64(Node->DirKey),
|
|||
|
Node->Flags );
|
|||
|
|
|||
|
DbgPrint("%sShort = %wZ, Long = %wZ\n", SpaceBuf,
|
|||
|
&Node->ShortName,
|
|||
|
&Node->LongName );
|
|||
|
|
|||
|
DbgPrint("%sP = %x, R = %x, L = %x\n", SpaceBuf,
|
|||
|
RtlParent(&Node->CacheLinks),
|
|||
|
RtlRightChild(&Node->CacheLinks),
|
|||
|
RtlLeftChild(&Node->CacheLinks) );
|
|||
|
}
|
|||
|
#endif // TUNNELTEST
|
|||
|
|