Develop File System Mini Filter Driver Step By Step
minispy Minifilter Sample
Description
The minispy sample is a tool to monitor and log any I/O
and transaction activity that occurs in the system. This sample is similar to
the FileSpy legacy filter; however, unlike FileSpy, minispy has been implemented as a minifilter.
Theory
of Operation
minispy consists of both user-mode and kernel-mode components. The kernel-mode
component registers callback functions that correspond to various I/O and
transaction operations with the filter manager. These callback functions help minispy record any I/O and transaction activity occurring
in the system. When a user can request the recorded information, the recorded
information is passed to the user-mode component,
which can either output it on screen or log it to a file on disk.
To
observe I/O activity on a device, you must explicitly attach minispy to that device by using the minispy user-mode component. Similarly, you can request minispy to stop logging data for a particular device.
Implementation
and Design
You
should use this sample if you are developing a minifilter.
Building
the Sample
1.
Open the appropriate WDK free or check build environment to set
basic environment variables that the build utility needs.
2.
Navigate to the directory that contains the device source code
(for example, CD src\filesys\miniFilter\minispy).
3.
Run build -ceZor use the BCZ macro.
This behavior calls the Microsoft make routines that produce log files
called Buildxxx_yyy_zzz.log, Buildxx_yyy_zzz.wrn,
and Buildxxx_yyy_zzz.err if there are
any warnings or errors. xxx stands for "fre"
or "chk" depending on the environment you
choose, yyy stands for the operating
system version (for example, "Wlh" for
Windows Vista), and zzz stands for the
platform version (for example, "x86" for x86-based, "IA64"
for Itanium-based, or "AMD64" for x64-based).
If
the build succeeds, the driver, minispy.sys, will be placed in a
platform-specific subdirectory of your %TargetPath% directory that is specified
in the sources file.
Installation
End User
The minispy minifilter comes
with an INF file that will install the minifilter. To
install the minifilter, do the following:
1.
Make sure that minispy.exe, minspy.sys, and minispy.inf are in the same directory.
2.
In Windows Explorer, right-click minispy.inf, and click Install.
This
installation will make the necessary registry updates to register the metadata
service and place minispy.sys in the %SystemRoot%\system32\drivers directory.
To
load this minifilter, run fltmc load minispy or net start minispy.
Writing a DriverEntry Routine for a Minifilter Driver
Every file
system minifilter driver must have a DriverEntry routine. The DriverEntry routine is called when the minifilter driver is loaded.
The DriverEntry routine performs global initialization, registers the minifilter driver, and initiates filtering. This routine runs in a system thread context
at IRQL PASSIVE_LEVEL.
The DriverEntry routine is defined as follows:
NTSTATUS
(*PDRIVER_INITIALIZE) (
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
);
DriverEntry has two input parameters. The first, DriverObject,
is the driver object that was created when the minifilter driver was loaded. The second, RegistryPath, is a pointer to a counted Unicode
string that contains a path to the minifilter driver's registry key.
A minifilter driver's DriverEntry routine must perform the
following steps, in order:
1.
Perform any needed global initialization for the minifilter driver.
2.
Register the minifilter driver by calling FltRegisterFilter.
3.
Initiate filtering by calling FltStartFiltering.
4.
Return an appropriate NTSTATUS value.
Registering the Minifilter Driver
Every minifilter driver must call FltRegisterFilter from its DriverEntry routine to add itself to
the global list of registered minifilter drivers and
to provide the filter manager with a list of callback routines and other
information about the driver.
In
the MiniSpy sample, the minifilter driver is registered as shown in the following code
example:
status = FltRegisterFilter(
DriverObject, //Driver
&FilterRegistration, //Registration
&MiniSpyData.FilterHandle); //RetFilter
FltRegisterFilter has two input parameters. The
first, Driver, is the driver object pointer that the minifilter driver received as the DriverObject input
parameter to its DriverEntry routine. The
second, Registration, is a pointer to an FLT_REGISTRATION structure that
contains entry points to the minifilter driver's
callback routines.
In
addition, FltRegisterFilter has an output
parameter, RetFilter, that receives an opaque filter pointer for the minifilter driver. This filter pointer is a required input
parameter for many FltXxx support
routines, including FltStartFiltering and FltUnregisterFilter.
Initiating Filtering
After
calling FltRegisterFilter, a minifilter driver's DriverEntry routine typically calls FltStartFiltering to
begin filtering I/O operations.
Every minifilter driver must call FltStartFiltering from its DriverEntry routine to notify the
filter manager that the minifilter driver is ready to
begin attaching to volumes and filtering I/O requests. After the minifilter driver calls FltStartFiltering,
the filter manager treats the minifilter driver as a
fully active minifilter driver, presenting it with
I/O requests and notifications of volumes to attach to.
The minifilter driver must be prepared to begin
receiving these I/O requests and notifications even before FltStartFiltering returns.
In
the MiniSpy sample driver, FltStartFiltering is called as shown in the following code example:
if( !NT_SUCCESS( status )) {
FltUnregisterFilter( MiniSpyData.FilterHandle );
}
If
the call to FltStartFiltering does not return
STATUS_SUCCESS, the minifilter driver must call FltUnregisterFilter to unregister itself.
Returning Status from a Minifilter DriverEntry Routine
A minifilter driver's DriverEntry routine normally returns STATUS_SUCCESS. But if minifilter initialization fails, the DriverEntry routine should return an appropriate error NTSTATUS value.
If
the DriverEntry routine returns a status value
that is not a success NTSTATUS value, the system responds by unloading the minifilter driver. The minifilter driver's FilterUnloadCallback routine is not called. For this reason, the DriverEntry routine must free any memory that was allocated for
system resources before returning a status value that is not a success NTSTATUS
value.
//---------------------------------------------------------------------------
// ROUTINES
//---------------------------------------------------------------------------
NTSTATUS
DriverEntry
(
__in PDRIVER_OBJECT
DriverObject,
__in PUNICODE_STRING
RegistryPath
)
/*++
Routine Description:
This
routine is called when a driver first loads. Its purpose is to
initialize global state and then register with FltMgr to start
filtering.
Arguments:
DriverObject - Pointer to driver object created by the system to
represent this driver.
RegistryPath - Unicode string identifying where the parameters for this
driver are located in the registry.
Return Value:
Status of the operation.
--*/
{
PSECURITY_DESCRIPTOR sd;
OBJECT_ATTRIBUTES oa;
UNICODE_STRING uniString;
NTSTATUS status =
STATUS_SUCCESS;
try {
//
// Initialize global data structures.
//
MiniSpyData.LogSequenceNumber = 0;
MiniSpyData.MaxRecordsToAllocate = DEFAULT_MAX_RECORDS_TO_ALLOCATE;
MiniSpyData.RecordsAllocated = 0;
MiniSpyData.NameQueryMethod = DEFAULT_NAME_QUERY_METHOD;
MiniSpyData.DriverObject = DriverObject;
InitializeListHead( &MiniSpyData.OutputBufferList );
KeInitializeSpinLock( &MiniSpyData.OutputBufferLock );
ExInitializeNPagedLookasideList( &MiniSpyData.FreeBufferList,
NULL,
NULL,
0,
RECORD_SIZE,
SPY_TAG,
0 );
#if MINISPY_VISTA
//
// Dynamically
import FilterMgr APIs for transaction support
//
#pragma warning(push)
#pragma warning(disable:4055) // type cast
from data pointer to function pointer
MiniSpyData.PFltSetTransactionContext = (PFLT_SET_TRANSACTION_CONTEXT)
FltGetRoutineAddress( "FltSetTransactionContext" );
MiniSpyData.PFltGetTransactionContext = (PFLT_GET_TRANSACTION_CONTEXT)
FltGetRoutineAddress( "FltGetTransactionContext" );
MiniSpyData.PFltEnlistInTransaction = (PFLT_ENLIST_IN_TRANSACTION)
FltGetRoutineAddress( "FltEnlistInTransaction" );
#pragma warning(pop)
#endif
//
// Read the custom parameters for MiniSpy from the registry
//
SpyReadDriverParameters(RegistryPath);
//
// Now that our
global configuration is complete, register with FltMgr.
//
status = FltRegisterFilter( DriverObject,
&FilterRegistration,
&MiniSpyData.Filter );
if (!NT_SUCCESS( status )) {
leave;
}
status =
FltBuildDefaultSecurityDescriptor( &sd,
FLT_PORT_ALL_ACCESS );
if (!NT_SUCCESS( status )) {
leave;
}
RtlInitUnicodeString( &uniString, MINISPY_PORT_NAME );
InitializeObjectAttributes( &oa,
&uniString,
OBJ_KERNEL_HANDLE
| OBJ_CASE_INSENSITIVE,
NULL,
sd );
status = FltCreateCommunicationPort( MiniSpyData.Filter,
&MiniSpyData.ServerPort,
&oa,
NULL,
SpyConnect,
SpyDisconnect,
SpyMessage,
1 );
FltFreeSecurityDescriptor( sd );
if (!NT_SUCCESS( status )) {
leave;
}
//
// We are now
ready to start filtering
//
status = FltStartFiltering( MiniSpyData.Filter );
} finally {
if (!NT_SUCCESS( status ) ) {
if (NULL != MiniSpyData.ServerPort) {
FltCloseCommunicationPort( MiniSpyData.ServerPort );
}
if (NULL != MiniSpyData.Filter) {
FltUnregisterFilter( MiniSpyData.Filter );
}
ExDeleteNPagedLookasideList( &MiniSpyData.FreeBufferList );
}
}
return status;
}
Writing
a FilterUnloadCallback Routine for a Minifilter Driver
A file system minifilter driver can optionally register a
Minifilter drivers are not required to register a FilterUnloadCallback routine. However, we strongly
recommend that a minifilter driver registers this callback routine, because if a minifilter driver
does not register a FilterUnloadCallback routine, the driver cannot be unloaded.
To
register this callback routine, the minifilter driver
stores the address of a PFLT_FILTER_UNLOAD_CALLBACK-typed routine in the FilterUnloadCallback member of the
The FilterUnloadCallback routine is defined as follows:
typedef NTSTATUS
(*PFLT_FILTER_UNLOAD_CALLBACK) (
FLT_FILTER_UNLOAD_FLAGS Flags
);
The FilterUnloadCallback routine has one input parameter, Flags, which can be NULL or
FLTFL_FILTER_UNLOAD_MANDATORY. The filter manager sets this parameter to
FLTFL_FILTER_UNLOAD_MANDATORY to indicate that the unload operation is
mandatory. For more information about this parameter, see
A minifilter driver's FilterUnloadCallback routine must
perform the following steps:
·
Close any open kernel-mode communication server port handles.
·
Call
·
Perform any needed global cleanup.
·
Return an appropriate NTSTATUS value.
Closing the Communication Server Port
If
the minifilter driver previously opened a kernel-mode
communication server port by calling
If
a user-mode application has an open connection to the communication server
port, any client port for that connection will remain open after FltCloseCommunicationPort returns. However, the
filter manager will close any client ports when the minifilter driver is unloaded.
Unregistering the Minifilter
A minifilter driver's
·
The minifilter driver's callback routines are unregistered.
·
The minifilter driver's instances are torn down, and the minifilter driver's
·
If the minifilter driver set any contexts on volumes, instances, streams, or stream handles,
these contexts are deleted. If the minifilter driver has registered a
If
there are outstanding rundown references on the minifilter driver's opaque filter pointer, FltUnregisterFilter enters a wait state until they are removed.
Outstanding rundown references usually happen because the minifilter driver has called
Outstanding
rundown references can also happen if the minifilter driver has called any routines that add a rundown reference to the minifilter driver's opaque filter pointer, such as
Performing Global Cleanup
A minifilter driver's
·
Call
·
Call
·
Call
·
Call
Returning Status from a FilterUnloadCallback Routine
A minifilter driver's
To
refuse an unload operation that is not mandatory, the minifilter driver should return an appropriate warning or error NTSTATUS value such as
STATUS_FLT_DO_NOT_DETACH. For more information about mandatory unload
operations, see Writing a FilterUnloadCallback Routine and
If
the FilterUnloadCallback routine returns a
warning or error NTSTATUS value and the unload operation is not mandatory, the minifilter driver will not be unloaded.
NTSTATUS
SpyFilterUnload
(
__in FLT_FILTER_UNLOAD_FLAGS
Flags
)
/*++
Routine Description:
This
is called when a request has been made to unload the filter. Unload
requests from the Operation System (ex: "sc stop minispy" can
not be
failed. Other unload
requests may be failed.
You
can disallow OS unload request by setting the
FLTREGFL_DO_NOT_SUPPORT_SERVICE_STOP flag in the FLT_REGISTARTION
structure.
Arguments:
Flags - Flags pertinent to this operation
Return Value:
Always success
--*/
{
UNREFERENCED_PARAMETER(
Flags );
PAGED_CODE();
//
// Close the server port. This will stop
new connections.
//
FltCloseCommunicationPort(
MiniSpyData.ServerPort );
FltUnregisterFilter(
MiniSpyData.Filter );
SpyEmptyOutputBufferList();
ExDeleteNPagedLookasideList(
&MiniSpyData.FreeBufferList );
return STATUS_SUCCESS;
}
Writing Preoperation and Postoperation Callback Routines
In
its DriverEntry routine, a minifilter driver can register up to one
Unlike
a legacy file system filter driver, a minifilter driver can choose which types of I/O operations
to filter. A minifilter driver can register a preoperation callback routine for a given type of I/O
operation without registering a postoperation callback, and vice versa. The minifilter driver
receives only those I/O operations for which it has registered a preoperation or postoperation callback routine.
A preoperation callback routine is
similar to a dispatch routine in the legacy filter driver
model. When the filter manager processes an I/O operation, it calls the preoperation callback
routine of each minifilter driver in the minifilter driver instance stack that has registered one for this type of I/O
operation. The topmost minifilter driver in the
stack—that is, the one whose instance has the highest altitude—receives the
operation first. When that minifilter driver finishes
processing the operation, it returns the operation to the filter manager, which
then passes the operation to the next-highest minifilter driver, and so on. When all minifilter drivers in the minifilter driver instance stack have processed the I/O operation—unless a minifilter driver has completed the I/O operation—the
filter manager sends the operation to legacy filters and the file system.
A postoperation callback routine is
similar to a completion routine in the legacy filter driver
model. Completion processing for an I/O operation begins when the I/O
manager passes the operation to the file system and legacy filters that have
registered completion routines for the operation. After these completion
routines have finished, the filter manager performs completion processing for
the operation. The filter manager then calls the postoperation callback routine of each minifilter driver in the minifilter driver instance stack that has registered one for this type of I/O
operation. The bottom minifilter driver in the
stack—that is, the one whose instance has the lowest altitude—receives the
operation first. When that minifilter driver finishes
processing the operation, it returns it to the filter manager, which then
passes the operation to the next-lowest minifilter driver, and so on.
Registering Preoperation and Postoperation Callback Routines
To
register
Each
FLT_OPERATION_REGISTRATION structure in the array, except for the last one,
contains the following information:
·
The major function code for the
operation
·
For read and write operations
(IRP_MJ_READ and IRP_MJ_WRITE), a set of flags that specify whether to ignore
cached I/O or paging I/O or both for IRP-based I/O operations
·
Entry points for up to one preoperation callback routine and one postoperation callback routine
The
last element in the array must be {IRP_MJ_OPERATION_END}.
The
following code example, which is taken from the
Scanner sample minifilter driver, shows an array of
FLT_OPERATION_REGISTRATION structures. The Scanner sample minifilter driver registers preoperation and postoperation callback routines for IRP_MJ_CREATE and preoperation callback routines for IRP_MJ_CLEANUP and IRP_MJ_WRITE operations.
{IRP_MJ_CREATE,
0,
ScannerPreCreate,
ScannerPostCreate},
{IRP_MJ_CLEANUP,
0,
ScannerPreCleanup,
NULL},
{IRP_MJ_WRITE,
0,
ScannerPreWrite,
NULL},
{IRP_MJ_OPERATION_END}
};
Filtering I/O Operations in a Minifilter Driver
The
following list describes several guidelines for filtering specific types of I/O
operations in a file system minifilter driver:
·
The
·
The
·
Minifilter drivers must never fail IRP_MJ_CLEANUP or IRP_MJ_CLOSE
operations. These operations can be pended, returned to the
filter manager, or completed with STATUS_SUCCESS. However, a preoperation callback routine must never fail these
operations.
·
Minifilter drivers cannot register a postoperation callback routine for IRP_MJ_SHUTDOWN.
Writing Preoperation Callback Routines
A file system minifilter driver uses one or more preoperation callback
routines to filter I/O operations.
A minifilter driver registers a preoperation callback routine for a particular type of I/O operation by storing the callback
routine's entry point in the OperationRegistration member of the
Minifilter drivers receive only those types of I/O
operations for which they have registered a preoperation or postoperation callback routine. A minifilter driver can register a preoperation callback routine for a given type of I/O operation without registering a
Every preoperation callback routine is
defined as follows:
(*PFLT_PRE_OPERATION_CALLBACK) (
IN OUT PFLT_CALLBACK_DATA Data,
IN PCFLT_RELATED_OBJECTS FltObjects,
OUT PVOID *CompletionContext
);
Like
a dispatch routine, a preoperation callback routine
can be called at IRQL = PASSIVE_LEVEL or at IRQL = APC_LEVEL. Typically it is
called at IRQL = PASSIVE_LEVEL, in the context of the thread that originated
the I/O request. For fast I/O and file system filter (FsFilter)
operations, the preoperation callback routine is
always called at IRQL = PASSIVE_LEVEL. However, for an IRP-based operation, a minifilter driver's preoperation callback routine can be called in the context of a
system worker thread if a higher filter or minifilter driver pends the operation for processing by the worker thread.
When
the filter manager calls a minifilter driver's preoperation callback routine for a given I/O operation,
the minifilter driver temporarily controls the I/O
operation. The minifilter driver retains this control
until it does one of the following:
·
Returns
a status value other than FLT_PREOP_PENDING from the preoperation callback routine.
·
Calls
Passing an I/O Operation Down the Minifilter Driver
Instance Stack
When
a minifilter driver's
A minifilter driver's preoperation callback routine returns an I/O operation to the
filter manager for further processing by returning one of the following status
values:
·
FLT_PREOP_SUCCESS_NO_CALLBACK
(all operation types)
·
FLT_PREOP_SUCCESS_WITH_CALLBACK
(all operation types)
·
FLT_PREOP_SYNCHRONIZE
(IRP-based I/O operations only)
Note Although FLT_PREOP_SYNCHRONIZE should be
returned only for IRP-based I/O operations, you can return this status
value for other operation types. If it is returned for
an I/O operation that is not an IRP-based I/O operation, the filter manager
treats this return value as if it were FLT_PREOP_SUCCESS_WITH_CALLBACK.
Alternatively,
the work routine for an operation that was pended in a preoperation callback routine returns an I/O operation to the
filter manager by passing one of the preceding status values in the CallbackStatus parameter when it calls
Returning FLT_PREOP_SUCCESS_WITH_CALLBACK
If
a minifilter driver's
Note If the minifilter driver's preoperation callback routine returns
FLT_PREOP_SUCCESS_WITH_CALLBACK but the minifilter driver has not registered a postoperation callback routine
for the operation, the system asserts on a checked build.
If
the minifilter driver's preoperation callback routine returns FLT_PREOP_SUCCESS_WITH_CALLBACK, it can return a
non-NULL value in its CompletionContext output
parameter. This parameter is an optional context pointer that is passed to the corresponding postoperation callback routine. The postoperation callback routine
receives this pointer in its CompletionContext input parameter.
The
FLT_PREOP_SUCCESS_WITH_CALLBACK status value can be returned for all types of I/O operations.
Returning FLT_PREOP_SUCCESS_NO_CALLBACK
If
a minifilter driver's
If
the minifilter driver's preoperation callback routine returns FLT_PREOP_SUCCESS_NO_CALLBACK, it must return NULL in
its CompletionContext output parameter.
The
FLT_PREOP_SUCCESS_NO_CALLBACK status value can be returned for all types of I/O operations.
Returning FLT_PREOP_SYNCHRONIZE
If
a minifilter driver's
The
filter manager calls the minifilter driver's postoperation callback routine in the same thread context
as the preoperation callback, at IRQL <=
APC_LEVEL. (Note that this thread context is not necessarily the context of the
originating thread.)
Note If the minifilter driver's preoperation callback routine returns
FLT_PREOP_SYNCHRONIZE, but the minifilter driver has
not registered a postoperation callback routine for
the operation, the system asserts on a checked build.
If
the minifilter driver's preoperation callback routine returns FLT_PREOP_SYNCHRONIZE, it can return a non-NULL value
in its CompletionContext output parameter.
This parameter is an optional context pointer that is passed to the corresponding postoperation callback routine.
The postoperation callback routine receives this
pointer in its CompletionContext input
parameter.
A minifilter driver's preoperation callback routine should return FLT_PREOP_SYNCHRONIZE only for IRP-based I/O
operations. However, this status value can be returned for other operation types. If it is returned for an
I/O operation that is not an IRP-based I/O operation, the filter manager treats
this return value as if it were FLT_PREOP_SUCCESS_WITH_CALLBACK. To determine
whether an operation is an IRP-based I/O operation, use the
Minifilter drivers should not return
FLT_PREOP_SYNCHRONIZE for create operations, because these operations are
already synchronized by the filter manager. If a minifilter driver has registered preoperation and postoperation callback routines for IRP_MJ_CREATE
operations, the post-create callback routine is called at IRQL = PASSIVE_LEVEL,
in the same thread context as the pre-create callback routine.
Minifilter drivers must never return
FLT_PREOP_SYNCHRONIZE for asynchronous read or write operations. Doing so can
severely degrade both minifilter driver and system
performance and can even cause deadlocks if, for example, the modified page
writer thread is blocked. Before returning FLT_PREOP_SYNCHRONIZE for an
IRP-based read or write operation, a minifilter driver should verify that the operation is synchronous by calling
The
following types of I/O operations cannot be synchronized:
·
Oplock file system control (FSCTL) operations (MajorFunction is IRP_MJ_FILE_SYSTEM_CONTROL; FsControlCode is
·
Notify change directory
operations (MajorFunction is
IRP_MJ_DIRECTORY_CONTROL; MinorFunction is
IRP_MN_NOTIFY_CHANGE_DIRECTORY.)
·
Byte-range lock requests (MajorFunction is IRP_MJ_LOCK_CONTROL; MinorFunction is IRP_MN_LOCK.)
FLT_PREOP_SYNCHRONIZE cannot be returned for any of these operations.
Completing an I/O Operation in a Preoperation Callback Routine
To complete an I/O operation means to halt processing for the operation,
assign it a final NTSTATUS value, and return it to the filter manager.
When
a minifilter driver completes an I/O operation, the
filter manager does the following:
·
Does not
send the operation to minifilter drivers below the
current minifilter driver, to legacy filters, or to
the file system.
·
Calls the
·
Does not call the current minifilter driver's postoperation callback routine for the operation, if one exists.
A minifilter driver's
1.
Setting the callback data structure's IoStatus.Status field to the final NTSTATUS value for the operation.
2.
Returning FLT_PREOP_COMPLETE.
A preoperation callback routine that completes an I/O
operation cannot set a non-NULL completion context (in the CompletionContext output parameter).
A minifilter driver can also complete an operation in
the work routine for a previously pended I/O operation by performing the
following steps:
1.
Setting the callback data structure's IoStatus.Status field to the final NTSTATUS value for the operation.
2.
Passing FLT_PREOP_COMPLETE in the CallbackStatus parameter when the work routine calls
When
completing an I/O operation, a minifilter driver must
set the callback data structure's IoStatus.Status field to the final NTSTATUS value for the operation, but this NTSTATUS value
cannot be STATUS_PENDING or STATUS_FLT_DISALLOW_FAST_IO. For a cleanup or close
operation, the field must be STATUS_SUCCESS. These operations cannot be completed with any other NTSTATUS value.
Completing
an I/O operation is often referred to as succeeding or failing the operation, depending on the NTSTATUS value:
·
To succeed an I/O
operation means to complete it with a success or informational NTSTATUS value,
such as STATUS_SUCCESS.
·
To fail an I/O operation
means to complete it with an error or warning NTSTATUS value, such as
STATUS_INVALID_DEVICE_REQUEST or STATUS_BUFFER_OVERFLOW.
NTSTATUS
values are defined in ntstatus.h.
These values fall into four categories: success, informational, warning, and
error.
Disallowing a Fast I/O Operation in a Preoperation Callback Routine
In
certain circumstances, a minifilter driver might
choose to disallow a fast I/O operation instead of completing it. Disallowing a
fast I/O operation prevents the fast I/O path from being used for the operation.
Like
completing an I/O operation, disallowing a fast I/O operation means to halt
processing on it and return it to the filter manager. However, disallowing a
fast I/O operation is different from completing it. If a minifilter driver disallows a fast I/O operation that was issued by the I/O manager, the I/O manager may reissue the same operation as an
equivalent IRP-based operation.
When
a minifilter driver's
·
Does not
send the operation to minifilter drivers below the
current minifilter driver, to legacy filters, or to
the file system.
·
Calls the
·
Does not call the current minifilter driver's postoperation callback routine for the operation, if one exists.
A minifilter driver disallows a fast I/O operation by
returning FLT_PREOP_DISALLOW_FASTIO from the preoperation callback routine for the operation.
The preoperation callback routine should not set the
callback data structure's IoStatus.Status field, because the filter manager automatically sets this field to
STATUS_FLT_DISALLOW_FAST_IO.
FLT_PREOP_DISALLOW_FASTIO can only be returned for fast I/O operations. To
determine whether an operation is a fast I/O operation, see
Minifilter drivers cannot return
FLT_PREOP_DISALLOW_FASTIO for IRP_MJ_SHUTDOWN, IRP_MJ_VOLUME_MOUNT, or
IRP_MJ_VOLUME_DISMOUNT operations.
Pending an I/O Operation in a Preoperation Callback Routine
A minifilter driver's
A minifilter driver's preoperation callback routine pends an I/O operation by performing the following steps:
1.
Posting the I/O operation to a system work queue by calling a
routine such as
2.
Returning FLT_PREOP_PENDING.
A minifilter driver that must pend all (or most) incoming I/O operations should not use routines such as FltQueueDeferredIoWorkItem to pend operations,
because calling this routine can cause the system work queues to be flooded.
Instead, such a minifilter driver should use a
cancel-safe queue. For more information about using cancel-safe queues, see
Note
that the call to FltQueueDeferredIoWorkItem will fail if any of the following conditions are true:
·
The operation is not an
IRP-based I/O operation.
·
The operation is a paging I/O
operation.
·
The TopLevelIrp field of the current thread is not NULL. (For more information about how to
find the value of this field, see
·
The target instance for the I/O
operation is being torn down.
If
the minifilter driver's preoperation callback routine returns FLT_PREOP_PENDING, it must return NULL in the CompletionContext output parameter.
A minifilter driver can return FLT_PREOP_PENDING only
for IRP-based I/O operations. To determine whether an operation is an IRP-based
I/O operation, use the
The
work routine that dequeues and processes the I/O
operation must call FltCompletePendedPreOperation to resume processing for the operation.
Writing Postoperation Callback Routines
A file system minifilter driver uses one or more postoperation callback
routines to filter I/O operations.
A minifilter driver registers a postoperation callback routine for a particular type of I/O operation in the same way it
registers a
Minifilter drivers receive only those types of I/O
operations for which they have registered a preoperation or postoperation callback routine. A minifilter driver can register a preoperation callback routine for a given type of I/O operation without registering a postoperation callback, and vice versa.
Every postoperation callback routine is
defined as follows:
(*PFLT_POST_OPERATION_CALLBACK) (
IN OUT PFLT_CALLBACK_DATA Data,
IN PCFLT_RELATED_OBJECTS FltObjects,
IN PVOID CompletionContext,
IN FLT_POST_OPERATION_FLAGS Flags
);
Like
a completion routine, a postoperation callback
routine is called at IRQL <= DISPATCH_LEVEL, in an arbitrary thread context.
Because
it can be called at IRQL = DISPATCH_LEVEL, a postoperation callback routine cannot call kernel-mode routines that must be called at a
lower IRQL, such as
The
following situations are several exceptions to the preceding rule:
·
If a minifilter driver's preoperation callback routine returns
FLT_PREOP_SYNCHRONIZE for an IRP-based I/O operation, the corresponding postoperation callback routine is called at IRQL <=
APC_LEVEL, in the same thread context as the preoperation callback routine.
·
The postoperation callback routine for a fast I/O operation is called at IRQL = PASSIVE_LEVEL, in
the same thread context as the preoperation callback
routine.
·
Post-create callback routines
are called at IRQL = PASSIVE_LEVEL, in the context of the thread that
originated the IRP_MJ_CREATE operation.
When
the filter manager calls a minifilter driver's postoperation callback routine for a given I/O operation,
the minifilter driver temporarily controls the I/O
operation. The minifilter driver retains this control
until it does one of the following:
·
Returns
FLT_POSTOP_FINISHED_PROCESSING from the postoperation callback routine.
·
Calls
Performing Completion Processing for an
I/O Operation
A minifilter driver's
In
addition, when a minifilter driver instance is being
torn down, the filter manager "drains" any I/O operations for which
the instance has received a
When
the FLTFL_POST_OPERATION_DRAINING flag is set, the minifilter driver must not perform normal completion processing. Instead, it should
perform only necessary cleanup, such as freeing memory that the minifilter driver allocated for the CompletionContext parameter in its preoperation callback routine, and
return FLT_POSTOP_FINISHED_PROCESSING.
Ensuring that Completion Processing is Performed at Safe IRQL
As
noted in Writing Postoperation Callback Routines, the
However,
for IRP-based I/O operations that are not already synchronized, minifilter drivers can use to two techniques to ensure that
completion processing is performed at IRQL <= APC_LEVEL.
The
first technique is for the postoperation callback
routine to pend the I/O operation until completion
processing can be performed at IRQL <= APC_LEVEL. This technique is described in Pending an I/O Operation in a Postoperation Callback Routine.
The
second technique is for the minifilter driver's postoperation callback routine to call
Pending an I/O Operation in a Postoperation Callback Routine
A minifilter driver's
1.
Calling
2.
Calling
3.
Returning FLT_POSTOP_MORE_PROCESSING_REQUIRED.
Note
that the call to FltQueueDeferredIoWorkItem will fail if any of the following conditions are true:
·
The operation is not an
IRP-based I/O operation.
·
The operation is a paging I/O
operation.
·
The TopLevelIrp field of the current thread is not NULL. (For more information about how to
find the value of this field, see
·
The target instance for the I/O
operation is being torn down. (The filter manager
indicates this situation by setting the FLTFL_POST_OPERATION_DRAINING flag in
the Flags input parameter to the postoperation callback routine.)
Minifilter drivers must be prepared to handle this
failure. If your minifilter driver cannot handle such
failures, you should consider using the technique that is
described in Returning FLT_PREOP_SYNCHRONIZE instead of pending the I/O operation.
After
the minifilter driver's postoperation callback routine returns FLT_POSTOP_MORE_PROCESSING_REQUIRED, the filter
manager will not perform any further completion processing for the I/O
operation until the minifilter driver's work routine
calls
The
work routine that dequeues and performs completion
processing for the I/O operation must call FltCompletePendedPostOperation to return control of the operation to the filter manager.
Failing an I/O Operation in a Postoperation Callback Routine
A minifilter driver's
For
example, a minifilter driver's post-create callback
routine can fail a successful IRP_MJ_CREATE operation by performing the
following steps:
1.
Calling
2.
Setting the callback data structure's IoStatus.Status field to the final NTSTATUS value for the operation. This value must be a valid error NTSTATUS value, such as
STATUS_ACCESS_DENIED.
3.
Setting the callback data structure's IoStatus.Information field to zero.
4.
Returning FLT_POSTOP_FINISHED_PROCESSING.
When
setting the callback data structure's IoStatus.Status field to the final NTSTATUS value for the operation, the minifilter driver must specify a valid error NTSTATUS value. Note that minifilter drivers cannot specify STATUS_FLT_DISALLOW_FAST_IO; only the filter manager can use this NTSTATUS value.
Callers
of FltCancelFileOpen must be running at IRQL
<= APC_LEVEL. However, a minifilter driver can
safely call this routine from a post-create callback routine, because, for
IRP_MJ_CREATE operations, the postoperation callback
routine is called at IRQL = PASSIVE_LEVEL, in the context of the thread that
originated the create operation.
Modifying the Parameters for an I/O
Operation
A minifilter driver can modify the parameters for an
I/O operation. For example, a minifilter driver's
The
parameters for an I/O operation are found in the
callback data (
If
a minifilter driver's preoperation callback routine modifies the parameters for an I/O operation, all minifilter drivers below that minifilter driver in the minifilter driver instance stack will receive the modified parameters
in their preoperation and postoperation callback routines.
The
modified parameters are not received by the current minifilter driver's postoperation callback routine or by any minifilter drivers above
that minifilter driver in the minifilter driver instance stack. In all situations, a minifilter driver's preoperation and postoperation callback routines receive the same input parameter values for a given I/O
operation.
After
modifying the parameters for an I/O operation, the preoperation or postoperation callback routine must indicate that
it has done so by calling
If
a minifilter driver's preoperation callback routine modifies the parameters for an I/O operation, all minifilter drivers below that minifilter driver in the minifilter driver instance stack will receive the modified parameters
in the Data and FltObjects input
parameters to their preoperation and postoperation callback routines. (Minifilter drivers cannot directly modify the contents of the
Although
any parameter changes that a minifilter driver's preoperation callback routine makes are not received by the minifilter driver's own postoperation callback routine, a preoperation callback routine is
able to pass information about changed parameters to the minifilter driver's own postoperation callback routine. If the preoperation callback routine passes the I/O operation down
the stack by returning FLT_PREOP_SUCCESS_WITH_CALLBACK or
FLT_PREOP_SYNCHRONIZE, it can store information about changed parameter values
into a minifilter driver–defined structure that is pointed to by the CompletionContext output parameter. The filter manager passes this structure pointer in the CompletionContext input parameter to the postoperation callback routine.
CONST
FLT_OPERATION_REGISTRATION Callbacks[] = {
{ IRP_MJ_CREATE,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_CREATE_NAMED_PIPE,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_CLOSE,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_READ,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_WRITE,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_QUERY_INFORMATION,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_SET_INFORMATION,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_QUERY_EA,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_SET_EA,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_FLUSH_BUFFERS,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{
IRP_MJ_QUERY_VOLUME_INFORMATION,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{
IRP_MJ_SET_VOLUME_INFORMATION,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_DIRECTORY_CONTROL,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{
IRP_MJ_FILE_SYSTEM_CONTROL,
0,
SpyPreOperationCallback,
SpyPostOperationCallback
},
{ IRP_MJ_DEVICE_CONTROL,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{
IRP_MJ_INTERNAL_DEVICE_CONTROL,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_SHUTDOWN,
0,
SpyPreOperationCallback,
NULL }, //post operation callback not supported
{ IRP_MJ_LOCK_CONTROL,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_CLEANUP,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_CREATE_MAILSLOT,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_QUERY_SECURITY,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_SET_SECURITY,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_QUERY_QUOTA,
0,
SpyPreOperationCallback,
SpyPostOperationCallback
},
{ IRP_MJ_SET_QUOTA,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_PNP,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{
IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{
IRP_MJ_RELEASE_FOR_SECTION_SYNCHRONIZATION,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{
IRP_MJ_ACQUIRE_FOR_MOD_WRITE,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{
IRP_MJ_RELEASE_FOR_MOD_WRITE,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{
IRP_MJ_ACQUIRE_FOR_CC_FLUSH,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{
IRP_MJ_RELEASE_FOR_CC_FLUSH,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
/* {
IRP_MJ_NOTIFY_STREAM_FILE_OBJECT,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },*/
{
IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_NETWORK_QUERY_OPEN,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_MDL_READ,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_MDL_READ_COMPLETE,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_PREPARE_MDL_WRITE,
0,
SpyPreOperationCallback,
SpyPostOperationCallback
},
{ IRP_MJ_MDL_WRITE_COMPLETE,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_VOLUME_MOUNT,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_VOLUME_DISMOUNT,
0,
SpyPreOperationCallback,
SpyPostOperationCallback },
{ IRP_MJ_OPERATION_END }
};
const FLT_CONTEXT_REGISTRATION Contexts[]
= {
#if MINISPY_VISTA
{ FLT_TRANSACTION_CONTEXT,
0,
SpyDeleteTxfContext,
sizeof(MINISPY_TRANSACTION_CONTEXT),
'ypsM' },
#endif //
MINISPY_VISTA
{ FLT_CONTEXT_END }
};
//
// This defines
what we want to filter with FltMgr
//
CONST
FLT_REGISTRATION FilterRegistration = {
sizeof(FLT_REGISTRATION), // Size
FLT_REGISTRATION_VERSION, // Version
0, // Flags
Contexts, // Context
Callbacks, // Operation callbacks
SpyFilterUnload, // FilterUnload
NULL, // InstanceSetup
SpyQueryTeardown, // InstanceQueryTeardown
NULL, // InstanceTeardownStart
NULL, // InstanceTeardownComplete
NULL, // GenerateFileName
NULL, // GenerateDestinationFileName
NULL // NormalizeNameComponent
#if MINISPY_VISTA
,
SpyKtmNotificationCallback // KTM notification callback
#endif //
MINISPY_VISTA
};