/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    FiltrCtx.c

Abstract:

    This module provides three routines that allow filesystem filter drivers
    to associate state with FILE_OBJECTs -- for filesystems which support
    an extended FSRTL_COMMON_HEADER with FsContext.

    These routines depend on fields (FastMutext and FilterContexts)
    added at the end of FSRTL_COMMON_HEADER in NT 5.0.

    Filesystems should set FSRTL_FLAG2_SUPPORTS_FILTER_CONTEXTS if
    these new fields are supported.  They must also initialize the mutex
    and list head.

    Filter drivers must use a common header for the context they wish to
    associate with a file object:

        FSRTL_FILTER_CONTEXT:
                LIST_ENTRY  Links;
                PVOID       OwnerId;
                PVOID       InstanceId;

    The OwnerId is a bit pattern unique to each filter driver
    (e.g. the device object).

    The InstanceId is used to specify a particular instance of the context
    data owned by a filter driver (e.g. the file object).

Author:

    Dave Probert      [DavePr]    30-May-1997

Revision History:

    Neal Christiansen [nealch]    12-Jan-2001   Changed APIs to take 
                                                PFSRTL_ADVANCED_FCB_HEADER
                                                structures instead of
                                                FileObjects.

    Neal Christiansen [nealch]    19-Jan-2001   Added mutex lock to FsRtlTeardownFilterContexts
                                                because you can get filters
                                                trying to delete at the same 
                                                time the file system is trying
                                                to delete.

    Neal Christiansen [nealch]    25-Apr-2001   Added FileObject context routines
    Neal Christiansen [nealch]    25-Apr-2001   Marked all of this code as pageable
--*/

#include "FsRtlP.h"

#define MySearchList(pHdr, Ptr) \
    for ( Ptr = (pHdr)->Flink;  Ptr != (pHdr);  Ptr = Ptr->Flink )


//
//  The rest of the routines are not marked pageable so they can be called
//  during the paging path
//

NTKERNELAPI
VOID
FsRtlTeardownFilterContexts (
  IN PFSRTL_ADVANCED_FCB_HEADER AdvFcbHeader
  );


#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, FsRtlTeardownPerStreamContexts)
#pragma alloc_text(PAGE, FsRtlTeardownFilterContexts)
#pragma alloc_text(PAGE, FsRtlPTeardownPerFileObjectContexts)
#endif


//===========================================================================
//                  Handles Stream Contexts
//===========================================================================

NTKERNELAPI
NTSTATUS
FsRtlInsertPerStreamContext (
  IN PFSRTL_ADVANCED_FCB_HEADER AdvFcbHeader,
  IN PFSRTL_PER_STREAM_CONTEXT Ptr
  )
/*++

Routine Description:

    This routine associates filter driver context with a stream.

Arguments:

    AdvFcbHeader - Advanced FCB Header for stream of interest.

    Ptr - Pointer to the filter-specific context structure.
        The common header fields OwnerId and InstanceId should
        be filled in by the filter driver before calling.

Return Value:

    STATUS_SUCCESS - operation succeeded.

    STATUS_INVALID_DEVICE_REQUEST - underlying filesystem does not support
        filter contexts.

--*/

{
    if (!AdvFcbHeader || 
        !FlagOn(AdvFcbHeader->Flags2,FSRTL_FLAG2_SUPPORTS_FILTER_CONTEXTS))
    {

        return STATUS_INVALID_DEVICE_REQUEST;
    }

    ExAcquireFastMutex(AdvFcbHeader->FastMutex);

    InsertHeadList(&AdvFcbHeader->FilterContexts, &Ptr->Links);

    ExReleaseFastMutex(AdvFcbHeader->FastMutex);
    return STATUS_SUCCESS;
}


NTKERNELAPI
PFSRTL_PER_STREAM_CONTEXT
FsRtlLookupPerStreamContextInternal (
  IN PFSRTL_ADVANCED_FCB_HEADER AdvFcbHeader,
  IN PVOID         OwnerId     OPTIONAL,
  IN PVOID         InstanceId  OPTIONAL
  )
/*++

Routine Description:

    This routine lookups filter driver context associated with a stream.

    The macro FsRtlLookupFilterContext should be used instead of calling
    this routine directly.  The macro optimizes for the common case
    of an empty list.

Arguments:

    AdvFcbHeader - Advanced FCB Header for stream of interest.

    OwnerId - Used to identify context information belonging to a particular
        filter driver.

    InstanceId - Used to search for a particular instance of a filter driver
        context.  If not provided, any of the contexts owned by the filter
        driver is returned.

    If neither the OwnerId nor the InstanceId is provided, any associated
    filter context will be returned.

Return Value:

    A pointer to the filter context, or NULL if no match found.

--*/

{
    PFSRTL_PER_STREAM_CONTEXT ctx;
    PFSRTL_PER_STREAM_CONTEXT rtnCtx;
    PLIST_ENTRY list;

    ASSERT(AdvFcbHeader);
    ASSERT(FlagOn(AdvFcbHeader->Flags2,FSRTL_FLAG2_SUPPORTS_FILTER_CONTEXTS));

    ExAcquireFastMutex(AdvFcbHeader->FastMutex);
    rtnCtx = NULL;

    //
    // Use different loops depending on whether we are comparing both Ids or not.
    //

    if ( ARGUMENT_PRESENT(InstanceId) ) {

        MySearchList (&AdvFcbHeader->FilterContexts, list) {

            ctx  = CONTAINING_RECORD(list, FSRTL_PER_STREAM_CONTEXT, Links);
            if (ctx->OwnerId == OwnerId && ctx->InstanceId == InstanceId) {

                rtnCtx = ctx;
                break;
            }
        }

    } else if ( ARGUMENT_PRESENT(OwnerId) ) {

        MySearchList (&AdvFcbHeader->FilterContexts, list) {

            ctx  = CONTAINING_RECORD(list, FSRTL_PER_STREAM_CONTEXT, Links);
            if (ctx->OwnerId == OwnerId) {

                rtnCtx = ctx;
                break;
            }
        }

    } else if (!IsListEmpty(&AdvFcbHeader->FilterContexts)) {

        rtnCtx = (PFSRTL_PER_STREAM_CONTEXT)AdvFcbHeader->FilterContexts.Flink;
    }

    ExReleaseFastMutex(AdvFcbHeader->FastMutex);
    return rtnCtx;
}


NTKERNELAPI
PFSRTL_PER_STREAM_CONTEXT
FsRtlRemovePerStreamContext (
  IN PFSRTL_ADVANCED_FCB_HEADER AdvFcbHeader,
  IN PVOID         OwnerId     OPTIONAL,
  IN PVOID         InstanceId  OPTIONAL
  )
/*++

Routine Description:

    This routine deletes filter driver context associated with a stream.

    FsRtlRemoveFilterContext functions identically to FsRtlLookupFilterContext,
    except that the returned context has been removed from the list.

Arguments:

    AdvFcbHeader - Advanced FCB Header for stream of interest.

    OwnerId - Used to identify context information belonging to a particular
        filter driver.

    InstanceId - Used to search for a particular instance of a filter driver
        context.  If not provided, any of the contexts owned by the filter
        driver is removed and returned.

    If neither the OwnerId nor the InstanceId is provided, any associated
    filter context will be removed and returned.

Return Value:

    A pointer to the filter context, or NULL if no match found.

--*/

{
    PFSRTL_PER_STREAM_CONTEXT ctx;
    PFSRTL_PER_STREAM_CONTEXT rtnCtx;
    PLIST_ENTRY list;

    if (!AdvFcbHeader ||
        !FlagOn(AdvFcbHeader->Flags2,FSRTL_FLAG2_SUPPORTS_FILTER_CONTEXTS))
    {

        return NULL;
    }

    ExAcquireFastMutex(AdvFcbHeader->FastMutex);
    rtnCtx = NULL;

  // Use different loops depending on whether we are comparing both Ids or not.
    if ( ARGUMENT_PRESENT(InstanceId) ) {

        MySearchList (&AdvFcbHeader->FilterContexts, list) {

            ctx  = CONTAINING_RECORD(list, FSRTL_PER_STREAM_CONTEXT, Links);
            if (ctx->OwnerId == OwnerId && ctx->InstanceId == InstanceId) {

                rtnCtx = ctx;
                break;
            }
        }

    } else if ( ARGUMENT_PRESENT(OwnerId) ) {

        MySearchList (&AdvFcbHeader->FilterContexts, list) {

            ctx  = CONTAINING_RECORD(list, FSRTL_PER_STREAM_CONTEXT, Links);
            if (ctx->OwnerId == OwnerId) {

                rtnCtx = ctx;
                break;
            }
        }

    } else if (!IsListEmpty(&AdvFcbHeader->FilterContexts)) {

        rtnCtx = (PFSRTL_PER_STREAM_CONTEXT)AdvFcbHeader->FilterContexts.Flink;
    }

    if (rtnCtx) {
        RemoveEntryList(&rtnCtx->Links);   // remove the matched entry
    }

    ExReleaseFastMutex(AdvFcbHeader->FastMutex);
    return rtnCtx;
}


NTKERNELAPI
VOID
FsRtlTeardownPerStreamContexts (
  IN PFSRTL_ADVANCED_FCB_HEADER AdvFcbHeader
  )
/*++

Routine Description:

    This routine is called by filesystems to free the filter contexts
    associated with an FSRTL_COMMON_FCB_HEADER by calling the FreeCallback
    routine for each FilterContext.

Arguments:

    FilterContexts - the address of the FilterContexts field within
        the FSRTL_COMMON_FCB_HEADER of the structure being torn down
        by the filesystem.

Return Value:

    None.

--*/

{
    PFSRTL_PER_STREAM_CONTEXT ctx;
    PLIST_ENTRY ptr;
    BOOLEAN lockHeld;

    //
    //  Acquire the lock because someone could be trying to free this
    //  entry while we are trying to free it.
    //

    ExAcquireFastMutex( AdvFcbHeader->FastMutex );
    lockHeld = TRUE;

    try {

        while (!IsListEmpty( &AdvFcbHeader->FilterContexts )) {

            //
            //  Unlink the top entry then release the lock.  We must
            //  release the lock before calling the use or their could
            //  be potential locking order deadlocks.
            //

            ptr = RemoveHeadList( &AdvFcbHeader->FilterContexts );

            ExReleaseFastMutex(AdvFcbHeader->FastMutex);
            lockHeld = FALSE;

            //
            //  Call filter to free this entry
            //

            ctx = CONTAINING_RECORD( ptr, FSRTL_PER_STREAM_CONTEXT, Links );
            ASSERT(ctx->FreeCallback);

            (*ctx->FreeCallback)( ctx );

            //
            //  re-get the lock
            //

            ExAcquireFastMutex( AdvFcbHeader->FastMutex );
            lockHeld = TRUE;
        }

    } finally {

        if (lockHeld) {

            ExReleaseFastMutex( AdvFcbHeader->FastMutex );
        }
    }
}


//===========================================================================
//                  Handles FileObject Contexts
//===========================================================================

//
//  Internal structure used to manage the Per FileObject Contexts.
//

typedef struct _PER_FILEOBJECT_CTXCTRL {

    //
    //  This is a pointer to a Fast Mutex which may be used to
    //  properly synchronize access to the FsRtl header.  The
    //  Fast Mutex must be nonpaged.
    //

    FAST_MUTEX FastMutex;

    //
    // This is a pointer to a list of context structures belonging to
    // filesystem filter drivers that are linked above the filesystem.
    // Each structure is headed by FSRTL_FILTER_CONTEXT.
    //

    LIST_ENTRY FilterContexts;

} PER_FILEOBJECT_CTXCTRL, *PPER_FILEOBJECT_CTXCTRL;


NTKERNELAPI
NTSTATUS
FsRtlInsertPerFileObjectContext (
  IN PFILE_OBJECT FileObject,
  IN PFSRTL_PER_FILEOBJECT_CONTEXT Ptr
  )
/*++

Routine Description:

    This routine associates a context with a file object.

Arguments:

    FileObject - Specifies the file object of interest.

    Ptr - Pointer to the filter-specific context structure.
        The common header fields OwnerId and InstanceId should
        be filled in by the filter driver before calling.

Return Value:

    STATUS_SUCCESS - operation succeeded.

    STATUS_INVALID_DEVICE_REQUEST - underlying filesystem does not support
        filter contexts.

--*/

{
    PPER_FILEOBJECT_CTXCTRL ctxCtrl;
    NTSTATUS status;

    //
    //  Return if no file object
    //

    if (NULL == FileObject) {

        return STATUS_INVALID_PARAMETER;
    }

    if (!FsRtlSupportsPerFileObjectContexts(FileObject)) {

        return STATUS_INVALID_DEVICE_REQUEST;
    }

    //
    //  Get the context control structure out of the file object extension
    //

    ctxCtrl = IoGetFileObjectFilterContext( FileObject );

    if (NULL == ctxCtrl) {

        //
        //  There is not a control structure, allocate and initialize one
        //

        ctxCtrl = ExAllocatePoolWithTag( NonPagedPool,
                                         sizeof(PER_FILEOBJECT_CTXCTRL),
                                         'XCOF' );
        if (NULL == ctxCtrl) {

            return STATUS_INSUFFICIENT_RESOURCES;
        }

        ExInitializeFastMutex( &ctxCtrl->FastMutex );
        InitializeListHead( &ctxCtrl->FilterContexts );

        //
        //  Insert into the file object extension
        //

        status = IoChangeFileObjectFilterContext( FileObject,
                                                  ctxCtrl,
                                                  TRUE );

        if (!NT_SUCCESS(status)) {

            //
            //  If this operation fails it is because someone else inserted the
            //  entry at the same time.  In this case free the memory we
            //  allocated and re-get the current value.
            //

            ExFreePool( ctxCtrl );

            ctxCtrl = IoGetFileObjectFilterContext( FileObject );

            if (NULL == ctxCtrl) {

                //
                //  This should never actually happen.  If it does it means
                //  someone allocated and then freed a context very quickly.
                //

                ASSERT(!"This operation should not have failed");
                return STATUS_UNSUCCESSFUL;
            }
        }
    }

    ExAcquireFastMutex( &ctxCtrl->FastMutex );

    InsertHeadList( &ctxCtrl->FilterContexts, &Ptr->Links );

    ExReleaseFastMutex( &ctxCtrl->FastMutex );

    return STATUS_SUCCESS;
}


NTKERNELAPI
PFSRTL_PER_FILEOBJECT_CONTEXT
FsRtlLookupPerFileObjectContext (
  IN PFILE_OBJECT FileObject,
  IN PVOID OwnerId OPTIONAL,
  IN PVOID InstanceId OPTIONAL
  )
/*++

Routine Description:

    This routine lookups contexts associated with a file object.

Arguments:

    FileObject - Specifies the file object of interest.

    OwnerId - Used to identify context information belonging to a particular
        filter driver.

    InstanceId - Used to search for a particular instance of a filter driver
        context.  If not provided, any of the contexts owned by the filter
        driver is returned.

    If neither the OwnerId nor the InstanceId is provided, any associated
        filter context will be returned.

Return Value:

    A pointer to the filter context, or NULL if no match found.

--*/

{
    PPER_FILEOBJECT_CTXCTRL ctxCtrl;
    PFSRTL_PER_FILEOBJECT_CONTEXT ctx;
    PFSRTL_PER_FILEOBJECT_CONTEXT rtnCtx;
    PLIST_ENTRY list;

    //
    //  Return if no FileObjecty
    //

    if (NULL == FileObject) {

        return NULL;
    }

    //
    //  Get the context control structure out of the file object extension
    //

    ctxCtrl = IoGetFileObjectFilterContext( FileObject );

    if (NULL == ctxCtrl) {

        return NULL;
    }

    rtnCtx = NULL;
    ExAcquireFastMutex( &ctxCtrl->FastMutex );

    //
    //  Use different loops depending on whether we are comparing both Ids or not.
    //

    if ( ARGUMENT_PRESENT(InstanceId) ) {

        MySearchList (&ctxCtrl->FilterContexts, list) {

            ctx  = CONTAINING_RECORD(list, FSRTL_PER_FILEOBJECT_CONTEXT, Links);

            if ((ctx->OwnerId == OwnerId) && (ctx->InstanceId == InstanceId)) {

                rtnCtx = ctx;
                break;
            }
        }

    } else if ( ARGUMENT_PRESENT(OwnerId) ) {

        MySearchList (&ctxCtrl->FilterContexts, list) {

            ctx  = CONTAINING_RECORD(list, FSRTL_PER_FILEOBJECT_CONTEXT, Links);

            if (ctx->OwnerId == OwnerId) {

                rtnCtx = ctx;
                break;
            }
        }

    } else if (!IsListEmpty(&ctxCtrl->FilterContexts)) {

        rtnCtx = (PFSRTL_PER_FILEOBJECT_CONTEXT) ctxCtrl->FilterContexts.Flink;
    }

    ExReleaseFastMutex(&ctxCtrl->FastMutex);

    return rtnCtx;
}


NTKERNELAPI
PFSRTL_PER_FILEOBJECT_CONTEXT
FsRtlRemovePerFileObjectContext (
  IN PFILE_OBJECT FileObject,
  IN PVOID OwnerId OPTIONAL,
  IN PVOID InstanceId OPTIONAL
  )
/*++

Routine Description:

    This routine deletes contexts associated with a file object

    Filter drivers must explicitly remove all context they associate with
    a file object (otherwise the underlying filesystem will BugCheck at close).
    This should be done at IRP_CLOSE time.

    FsRtlRemoveFilterContext functions identically to FsRtlLookupFilterContext,
    except that the returned context has been removed from the list.

Arguments:

    FileObject - Specifies the file object of interest.

    OwnerId - Used to identify context information belonging to a particular
        filter driver.

    InstanceId - Used to search for a particular instance of a filter driver
        context.  If not provided, any of the contexts owned by the filter
        driver is removed and returned.

    If neither the OwnerId nor the InstanceId is provided, any associated
        filter context will be removed and returned.

Return Value:

    A pointer to the filter context, or NULL if no match found.

--*/

{
    PPER_FILEOBJECT_CTXCTRL ctxCtrl;
    PFSRTL_PER_FILEOBJECT_CONTEXT ctx;
    PFSRTL_PER_FILEOBJECT_CONTEXT rtnCtx;
    PLIST_ENTRY list;

    //
    //  Return if no file object
    //

    if (NULL == FileObject) {

        return NULL;
    }

    //
    //  Get the context control structure out of the file object extension
    //

    ctxCtrl = IoGetFileObjectFilterContext( FileObject );

    if (NULL == ctxCtrl) {

        return NULL;
    }

    rtnCtx = NULL;

    ExAcquireFastMutex( &ctxCtrl->FastMutex );

  // Use different loops depending on whether we are comparing both Ids or not.
    if ( ARGUMENT_PRESENT(InstanceId) ) {

        MySearchList (&ctxCtrl->FilterContexts, list) {

            ctx  = CONTAINING_RECORD(list, FSRTL_PER_FILEOBJECT_CONTEXT, Links);

            if ((ctx->OwnerId == OwnerId) && (ctx->InstanceId == InstanceId)) {

                rtnCtx = ctx;
                break;
            }
        }

    } else if ( ARGUMENT_PRESENT(OwnerId) ) {

        MySearchList (&ctxCtrl->FilterContexts, list) {

            ctx  = CONTAINING_RECORD(list, FSRTL_PER_FILEOBJECT_CONTEXT, Links);

            if (ctx->OwnerId == OwnerId) {

                rtnCtx = ctx;
                break;
            }
        }

    } else if (!IsListEmpty(&ctxCtrl->FilterContexts)) {

        rtnCtx = (PFSRTL_PER_FILEOBJECT_CONTEXT)ctxCtrl->FilterContexts.Flink;
    }

    if (rtnCtx) {

        RemoveEntryList(&rtnCtx->Links);   // remove the matched entry
    }

    ExReleaseFastMutex( &ctxCtrl->FastMutex );
    return rtnCtx;
}


VOID
FsRtlPTeardownPerFileObjectContexts (
  IN PFILE_OBJECT FileObject
  )
/*++

Routine Description:

    This routine is called by filesystems to free the filter contexts
    associated with an FSRTL_COMMON_FCB_HEADER by calling the FreeCallback
    routine for each FilterContext.

Arguments:

    FilterContexts - the address of the FilterContexts field within
        the FSRTL_COMMON_FCB_HEADER of the structure being torn down
        by the filesystem.

Return Value:

    None.

--*/

{
    PPER_FILEOBJECT_CTXCTRL ctxCtrl;
    NTSTATUS status;

    ASSERT(FileObject != NULL);

    ctxCtrl = IoGetFileObjectFilterContext( FileObject );

    if (NULL != ctxCtrl) {

        status = IoChangeFileObjectFilterContext( FileObject,
                                                  ctxCtrl,
                                                  FALSE );

        ASSERT(STATUS_SUCCESS == status);
        ASSERT(IsListEmpty( &ctxCtrl->FilterContexts));

        ExFreePool( ctxCtrl );
    }
}


LOGICAL
FsRtlIsPagingFile (
    IN PFILE_OBJECT FileObject
    )
/*++

Routine Description:

    This routine will return TRUE if the give file object is for a
    paging file.  It returns FALSE otherwise

Arguments:

    FileObject - The file object to test

Return Value:

    TRUE - if paging file
    FALSE - if not

--*/

{
    return MmIsFileObjectAPagingFile( FileObject );
}