/*++ 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 ); }