/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


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

--*/

#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 parameters (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


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.
//    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 TUNNEL *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 necessarily 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 TUNNEL *Cache,
    __in ULONGLONG DirKey,
    __in UNICODE_STRING *ShortName,
    __in UNICODE_STRING *LongName,
    __in BOOLEAN KeyByShortName,
    __in ULONG DataLength,
    __in_bcount(DataLength) VOID *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 tunneling 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 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);
    }

    //
    //  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 TUNNEL *Cache,
    __in ULONGLONG DirKey,
    __in UNICODE_STRING *Name,
    __out UNICODE_STRING *ShortName,
    __out UNICODE_STRING *LongName,
    __inout ULONG  *DataLength,
    __out_bcount_part(*DataLength, *DataLength) VOID *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 available 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 = NULL;
    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);

    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
                //
                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 TUNNEL *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 (presumably 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);

    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);
    }

    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 TUNNEL *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) {

            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);
        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;
}

