As I've said before, a minifilter can be either in the context of an IO operation or not. When the minifilter is not in the context of an IO operation or when it wants to send a request to a different device then the one it's currently processing IO on the minifilter can usually issue an IRP directly (though this is rather rare so if you need to issue an actual IRP from a minifilter then make sure there isn't something wrong about your design; layering problems have a nasty habit of not showing up until testing interop with other minifilters, which doesn't usually happen in early testing). In other words, a minifilter only needs to issue IO using the FltMgr framework in the same circumstances when it would call a FltXxx routine, which I've discussed here. There isn't anything special to issuing an IRP from a minifilter so I'll focus only on issuing a FLT_CALLBACK_DATA type IO.
The steps involved in issuing a FLT_CALLBACK_DATA are pretty straightforward:
- Allocate a FLT_CALLBACK_DATA structure by calling FltAllocateCallbackData().
- Initialize the FLT_CALLBACK_DATA structure for your IO. This primarily requires setting up the FLT_CALLBACK_DATA->Iopb structure. Don't forget to set up the MajorFunction and MinorFunction members.
- Send the request down to the filters below using FltPerformSynchronousIo() or FltPerformAsynchronousIo().
- Either free the FLT_CALLBACK_DATA (FltFreeCallbackData()) or reuse it (FltReuseCallbackData()), depending on whether you need to issue additional IO or not.
The MSDN documentation for the APIs is pretty thorough and combined with the powerpoint presentation from MS I think it covers the subject matter pretty well. However, there are some things I'd like to emphasize:NTSTATUS MyFltQueryDirectoryFile( __in PFLT_INSTANCE Instance, __in PFILE_OBJECT FileObject, __out PVOID FileInformation, __in ULONG Length, __in FILE_INFORMATION_CLASS FileInformationClass, __in BOOLEAN ReturnSingleEntry, __in_opt PUNICODE_STRING FileName, __in BOOLEAN RestartScan, __out_opt PULONG LengthReturned ) { NTSTATUS status = STATUS_SUCCESS; PFLT_CALLBACK_DATA callbackData = NULL; PFLT_PARAMETERS params = NULL; status = FltAllocateCallbackData( Instance, FileObject, &callbackData ); if (!NT_SUCCESS(status)) { return status; } callbackData->Iopb->MajorFunction = IRP_MJ_DIRECTORY_CONTROL; callbackData->Iopb->MinorFunction = IRP_MN_QUERY_DIRECTORY; if (RestartScan) { SetFlag( callbackData->Iopb->OperationFlags, SL_RESTART_SCAN ); } if (ReturnSingleEntry) { SetFlag( callbackData->Iopb->OperationFlags, SL_RETURN_SINGLE_ENTRY ); } params = &callbackData->Iopb->Parameters; params->DirectoryControl.QueryDirectory.Length = Length; params->DirectoryControl.QueryDirectory.FileName = FileName; params->DirectoryControl.QueryDirectory.FileInformationClass = FileInformationClass; params->DirectoryControl.QueryDirectory.FileIndex = 0; params->DirectoryControl.QueryDirectory.DirectoryBuffer = FileInformation; params->DirectoryControl.QueryDirectory.MdlAddress = NULL; FltPerformSynchronousIo( callbackData ); status = callbackData->IoStatus.Status; if (LengthReturned != NULL) { *LengthReturned = (ULONG)(callbackData->IoStatus.Information); } FltFreeCallbackData( callbackData ); return status; }
- You simply can't issue an IRP_MJ_CREATE this way, use FltCreateFile(Ex(2)).
- Both FltPerformSynchronousIo() and FltPerformAsynchronousIo() will set the FLT_CALLBACK_DATA->IoStatus.Status to reflect the status of the operation. Unfortunately, it's impossible to tell whether the request failed in FltMgr or if it was actually sent down and it failed in a lower layer. However, in the debugger one can tell whether the request failed in a lower layer by looking at the IRP associated with the FLT_CALLBACK_DATA. The filter allocated FLT_CALLBACK_DATA starts without being associated with an IRP so if the IRP is still NULL then that's an indication that the request failed in FltMgr. If the IRP is not null then it's possible to tell whether the IRP is completed or not and to see the status of the operation.
- FltPerformAsynchronousIo() will ALWAYS call the asynchronous completion routine. Basically, once a minifilter calls FltPerformAsynchronousIo() it is guaranteed one call to the async completion routine no matter what. So don't make assumptions that the async completion routine won't be called if the request fails in any way.
- The best way that I've found to figure out how to initialize a FLT_CALLBACK_DATA structure for a certain operation is to filter that operation and see what a FLT_CALLBACK_DATA generated by the FltMgr for an existing IRP looks like.
- For a call to FltPerformAsynchronousIo() that returned STATUS_PENDING, make sure to not free the FLT_CALLBACK_DATA until the IO actually completes. In fact, a good strategy is to call FltFreeCallbackData() from the async completion routine, which is guaranteed to be called only after the IO is complete.
- This could be a pretty useful feature to preallocate IRPs in the event that FltAllocateCallbackData() or some other FltXxx API fail because of low system resources and the filter wants to try to implement forward progress (for more discussion on forward progress in general see this page and the RamDisk WDK sample). However, since FltAllocateCallbackData() doesn't allocate the IRP associated for the FLT_CALLBACK_DATA structure, it's possible that even if one preallocates some FLT_CALLBACK_DATA structures to use for forward progress, the calls to FltPerformSynchronousIo() and FltPerformAsynchronousIo() might still fail when trying to allocate the IRP. This is why in Win7 FltMgr introduced FltAllocateCallbackDataEx() which allows a minifilter to preallocate a FLT_CALLBACK_DATA that is guaranteed to preallocate all the necessary memory thus enabling forward progress in a low-memory situation (see the explanation for the FLT_ALLOCATE_CALLBACK_DATA_PREALLOCATE_ALL_MEMORY flag).