Guidelines for Porting Legacy Filter Drivers

Developers are encouraged to port legacy filter drivers to the filter manager model to obtain better functionality for their filter drivers and improve system reliability. Experienced developers should find it relatively easy to port a legacy filter driver to a minifilter driver. Filter driver developers at Microsoft recommend the following approach:

  • Start with a reliable regression test suite to verify behavior between the legacy filter driver and the ported minifilter driver.

  • Create a minifilter driver shell and systematically move functionality from the legacy filter driver to the minifilter driver. For example, get attachment working and then port one operation at a time, testing after each operation.

  • Change user-mode/kernel-mode communication last, so you can use existing tools to test the minifilter driver.

  • Compile with PREfast and test with the Filter Verifier I/O verification option in Driver Verifier enabled.

During the porting process, you should review all legacy filter driver code to take full advantage of filter manager capabilities. In particular, keep the following in mind:

  • IRP-based I/O and fast I/O operations can come through the same operation when appropriate, which helps reduce duplication of code.

  • When registering for operations, a minifilter driver can explicitly choose to ignore all paging I/O and cached I/O, which eliminates the need for code to check these.

  • Instance notifications greatly simplify attach/detach logic.

  • Register only for operations that your minifilter driver must handle; you can ignore everything else.

  • Take advantage of filter manager context and name management support.

  • Take advantage of filter manager support for issuing non-recursive I/O.

  • Unlike legacy filter drivers, minifilter drivers cannot rely on local variables to maintain context from preoperation processing to postoperation processing. Consider allocating a lookaside list to store operation state.

  • Be sure to release references when finished with a name or context.

  • Completion ports in user mode add a powerful technique for building queues. You will probably need only a single connection to a single named port.

The following table lists common operations in a legacy filter driver and how they map to the filter manager model.

Legacy filter driver model Filter manager model

Pass-through operation with no completion routine

If your minifilter driver never does work for this type of I/O operation, do not register a preoperation or postoperation callback routine for this operation.

Otherwise, return FLT_PREOP_SUCCESS_NO_CALLBACK from the preoperation callback routine registered for this operation.

See Returning FLT_PREOP_SUCCESS_NO_CALLBACK.

Pass-through operation with a completion routine

Return FLT_PREOP_SUCCESS_WITH_CALLBACK from the preoperation callback routine.

See Returning FLT_PREOP_SUCCESS_WITH_CALLBACK.

Pend operation in the preoperation callback routine

Call FltLockUserBuffer as needed to ensure that any user buffers are properly locked so that they can be accessed in a worker thread.

Queue the work to a worker thread by calling support routines such as FltAllocateDeferredIoWorkItem and FltQueueDeferredIoWorkItem.

Return FLT_PREOP_PENDING from the preoperation callback routine.

When ready to return the I/O operation to the filter manager, call FltCompletePendedPreOperation.

See Pending an I/O Operation in a Preoperation Callback Routine.

Pend operation in the postoperation callback routine

In the preoperation callback routine, call FltLockUserBuffer to ensure that user buffers are properly locked so that they can be accessed in a worker thread.

Queue the work to a worker thread by calling support routines such as FltAllocateGenericWorkItem and FltQueueGenericWorkItem.

Return FLT_POSTOP_MORE_PROCESSING_REQUIRED from the postoperation callback routine.

When ready to return the I/O operation to the filter manager, call FltCompletePendedPostOperation.

See Pending an I/O Operation in a Postoperation Callback Routine.

Synchronize the operation

Return FLT_PREOP_SYNCHRONIZE from the preoperation callback routine.

See Returning FLT_PREOP_SYNCHRONIZE.

Complete the operation in the preoperation callback routine

Set the final operation status and information in the IoStatus member of the FLT_CALLBACK_DATA structure for the operation.

Return FLT_PREOP_COMPLETE from the preoperation callback routine.

See Completing an I/O Operation in a Preoperation Callback Routine.

Complete the operation after it has been pended in the preoperation callback routine

Set the final operation status and information in the IoStatus member of the FLT_CALLBACK_DATA structure for the operation.

Call FltCompletePendedPreOperation from the worker thread processing the I/O operation, passing FLT_PREOP_COMPLETE as the CallbackStatus parameter.

See Completing an I/O Operation in a Preoperation Callback Routine.

Do all completion work in the completion routine

Return FLT_POSTOP_FINISHED_PROCESSING from the postoperation callback routine.

See Writing Postoperation Callback Routines.

Do completion work at safe IRQL

Call FltDoCompletionProcessingWhenSafe from the postoperation callback routine.

See Ensuring that Completion Processing is Performed at Safe IRQL.

Signal an event from the completion routine

Return FLT_PREOP_SYNCHRONIZE from the preoperation callback routine for this operation.

The filter manager calls the postoperation callback routine in the same thread context as the preoperation callback routine, at IRQL <= APC_LEVEL.

See Returning FLT_PREOP_SYNCHRONIZE.

Fail a successful create operation

Call FltCancelFileOpen from the postoperation callback routine for the create operation.

Set an appropriate error NTSTATUS value in the IoStatus member of the FLT_CALLBACK_DATA structure for the operation.

Return FLT_POSTOP_FINISHED_PROCESSING.

See Failing an I/O Operation in a Postoperation Callback Routine.

Disallow I/O through the fast I/O path for an I/O operation

Return FLT_STATUS_DISALLOW_FAST_IO from the preoperation callback routine for the operation.

See Disallowing a Fast I/O Operation in a Preoperation Callback Routine.

Modify the parameters for an I/O operation

Set the modified parameter values in the Iopb member of the FLT_CALLBACK_DATA structure for the operation.

Mark the FLT_CALLBACK_DATA structure as dirty by calling FltSetCallbackDataDirty, except when you have modified the contents of the IoStatus member of the FLT_CALLBACK_DATA structure.

See Modifying the Parameters for an I/O Operation.

Lock the user buffer for the operation

Use the techniques and guidelines described in Accessing the User Buffers for an I/O Operation.