- Tunneling - Name Tunneling in Windows 2000 File Systems. The article is old but the information is still accurate. It explains why the tunnel cache exists and how file systems use it.
- Microsoft KB article 172190. This page explains how to experiment with the tunnel cache and how it can be adjusted. Make sure to change the time and size of cache if you're going to play with it manually, it might be hard to reproduce otherwise.
- The documentation page for FltGetTunneledName. This explains when to use this API.
This topic is very important to minifilter writers that have minifilters that use names. In particular minifilters that call FltGetFileNameInformation with FLT_FILE_NAME_NORMALIZED in preCreate (and there seem to be quite a few of those despite the various issues associated with this design) and in preSetInformation. But to explain why this is an issue we need to look at FltMgr's behavior in preCreate and during IRP_MJ_SET_INFORMATION.
As you're probably aware, FltMgr caches the names it generates every time someone asks for FltGetFileNameInformation. Because name generation is pretty expensive, a cache is a very good idea and performance does benefit greatly especially on system with multiple minifilters installed (and since Windows now ships with 2 minifilters and most people use anti-virus software which also use file system filters you can imagine that the performance benefits add up). The name cache is stored in a context associated with a stream (but it might be per FILE_OBJECT if there are multiple hardlinks to the file that the stream belongs to). However, in preCreate the stream is not yet opened. The IRP_MJ_CREATE operation must complete in the file system for the stream to be known and so FltMgr can't use the name cache in preCreate. This has two major implications:
- Performance will be affected since the name will have to be generated for every single IRP_MJ_CREATE (which is a pretty big reason why one shouldn't query names in preCreate if they can avoid it).
- Not only can the name not be looked up in the cache, but it also can't be stored in the cache. So now consider the case of 3 minifilters that all query the name in preCreate. If FltMgr didn’t cache the name at all it would have to build it three times. FltMgr does what it can in this case and it caches the name in an internal cache associated with the IRP_MJ_CREATE operation. Then, once the operation completes, the cache is transparently moved to the stream cache.
One other thing to consider is that the name generated in preCreate, even if the caller asks for FLT_FILE_NAME_NORMALIZED, might still contain a short name. This can happen when the IRP_MJ_CREATE is trying to create a new file (that doesn't exist yet) specifying only the short name. FltMgr tries to normalize the name and it gets a normalized path for the parent directory of that file, but the file itself is not in the directory and there is nothing FltMgr can do except return the short name to the caller for that file (the final component). This is in fact where the Name Tunneling Cache comes into play. In this case when the created reaches the file system, if it finds the short file name in the cache it will create the file with the long name from the cache as well. But as we've explained before the minifilter might have a normalized name from the preCreate which no longer matches the file.
In order to show when this happens (and to show some code using this function), I wrote a small modification on top of the passthrough minifilter sample from the WDK:
To recap, you need to care about this only if your minifilter queries normalized names in preCreate or preSetInformation and then uses them in the postOp callback. Also, you might need to worry about this if you keep a normalized name in a context. However, this is only a concern for renames (you can't use a context in preCreate) and for minifilters that keep a name in a context the easiest way is to update it in postRename and not do anything in preRename.FLT_PREOP_CALLBACK_STATUS PtPreOperationPassThrough ( __inout PFLT_CALLBACK_DATA Data, __in PCFLT_RELATED_OBJECTS FltObjects, __deref_out_opt PVOID *CompletionContext ) /*++ Routine Description: This routine is the main pre-operation dispatch routine for this miniFilter. Since this is just a simple passThrough miniFilter it does not do anything with the callbackData but rather return FLT_PREOP_SUCCESS_WITH_CALLBACK thereby passing it down to the next miniFilter in the chain. This is non-pageable because it could be called on the paging path Arguments: Data - Pointer to the filter callbackData that is passed to us. FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing opaque handles to this filter, instance, its associated volume and file object. CompletionContext - The context for the completion routine for this operation. Return Value: The return value is the status of the operation. --*/ { NTSTATUS status;
PFLT_FILE_NAME_INFORMATION name;
PT_DBG_PRINT( PTDBG_TRACE_ROUTINES, ("PassThrough!PtPreOperationPassThrough: Entered\n") );
if (Data->Iopb->MajorFunction == IRP_MJ_CREATE) { // // this is a preCreate, get the name. // status = FltGetFileNameInformation( Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &name ); if (NT_SUCCESS(status)) { // // send it to postCreate // *CompletionContext = (PVOID) name; } else { PT_DBG_PRINT( PTDBG_TRACE_FILE_NAME_FAILURES, ("PassThrough!PtPreOperationPassThrough: Failed to get file name, status=%08x\n", status) ); } }
// // See if this is an operation we would like the operation status // for. If so request it. // // NOTE: most filters do NOT need to do this. You only need to make // this call if, for example, you need to know if the oplock was // actually granted. // if (PtDoRequestOperationStatus( Data )) { status = FltRequestOperationStatusCallback( Data, PtOperationStatusCallback, (PVOID)(++OperationStatusCtx) ); if (!NT_SUCCESS(status)) { PT_DBG_PRINT( PTDBG_TRACE_OPERATION_STATUS, ("PassThrough!PtPreOperationPassThrough: FltRequestOperationStatusCallback Failed, status=%08x\n", status) ); } } return FLT_PREOP_SUCCESS_WITH_CALLBACK; } FLT_POSTOP_CALLBACK_STATUS PtPostOperationPassThrough ( __inout PFLT_CALLBACK_DATA Data, __in PCFLT_RELATED_OBJECTS FltObjects, __in_opt PVOID CompletionContext, __in FLT_POST_OPERATION_FLAGS Flags ) /*++ Routine Description: This routine is the post-operation completion routine for this miniFilter. This is non-pageable because it may be called at DPC level. Arguments: Data - Pointer to the filter callbackData that is passed to us. FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing opaque handles to this filter, instance, its associated volume and file object. CompletionContext - The completion context set in the pre-operation routine. Flags - Denotes whether the completion is successful or is being drained. Return Value: The return value is the status of the operation. --*/ {
NTSTATUS status; PFLT_FILE_NAME_INFORMATION name = NULL; PFLT_FILE_NAME_INFORMATION realName = NULL;
UNREFERENCED_PARAMETER( FltObjects ); UNREFERENCED_PARAMETER( Flags ); PT_DBG_PRINT( PTDBG_TRACE_ROUTINES, ("PassThrough!PtPostOperationPassThrough: Entered\n") );
name = (PFLT_FILE_NAME_INFORMATION)CompletionContext; if (name != NULL) { // // we got a name from preCreate. check if it's a tunneled name. // status = FltGetTunneledName( Data, name, &realName ); if (NT_SUCCESS(status)) { // // see if we actually got a tunneled name. // if (realName != NULL) { DbgPrint( "Got a tunneled name: File:%p, original name: %wZ, real name:%wZ\n", FltObjects->FileObject, &name->Name, &realName->Name ); } } FltReleaseFileNameInformation( name ); if (realName != NULL) { FltReleaseFileNameInformation( realName ); } }
return FLT_POSTOP_FINISHED_PROCESSING; }
This is definitely useful, particularly the performance implications of doing name lookups in IRP_MJ_CREATE. Any chance you can post something about GenerateFileNameCallback and NormalizeNameComponentCallback, and various 'gotcha' things that might affect one if you start changing the filenames?
ReplyDeleteI plan to show how to implement a simple name provider (and thus show a basic implementation for GenerateFileNameCallback and NormalizeNameComponentCallback) pretty soon.
ReplyDeleteUnfortunately, the gotchas depend primarily on the architecture of the solution so there is no way to cover them all.
I've seen a lot of mention of the issues of querying normalized names in PreCreate. I took that to heart and query for the normalized name in PostCreate, as I don't really care when I get the name, just so long as I can get it. It seems to work fine, but are there issues with that approach? If so, what are they?
ReplyDeleteHi Phil.
ReplyDeleteThis has the potential to be a long discussion. I wrote a post a while back on using names in file system filters (http://fsfilters.blogspot.com/2010/02/names-and-file-systems-filters.html) in which i wrote about some of the problems i see with various approaches.
If you compare getting a normalized name in postCreate to the same in preCreate, then I don't think there are any additional issues except that some designs really need a normalized name in preCreate. For example some solutions that use names to implement policy (files under a certain path must be read-only). However, even this could be implemented so that the opened name is used and then the lookup can take a non-normalized path for example.
So to answer your question, I don't know of any additional issues introduced by querying a normalized name in postCreate.