Thursday, March 8, 2012

Name Provider Changes

I just wanted to add a couple of things to my previous post on what a passthrough nameprovider should look like. There are two changes I'd like to make:
  1. The code I have posted for the PtGenerateFileNameCallback() function has an assert that it shouldn't receive a generate request for a normalized name. However, there are some changes in Win8 where the file system can now generate directly a normalized name (see my previous post on Name Normalization in Win8) and as such FltMgr has been changed and it can also now request a normalized name from in the generate name callback. So that assert needs to be changed to include a normalized name.
  2. Also in the code for PtGenerateFileNameCallback() I have omitted to add a very important flag, the FLT_FILE_NAME_DO_NOT_CACHE flag. This is a very important flag because it can lead to problems with FltMgr's name cache. It isn't really necessary for my example because the name above my filter is the same as the name below my filter, but it is very important for filters that change the name of a file. I'll explain this issue in more detail below.
So FltMgr's name cache is simply a FltMgr specific context associated with the stream (or with the FILE_OBJECT depending on some factors) where FltMgr remembers the name for the object. Because before Win8 building a name was such an expensive operation FltMgr wanted to cache the name whenever it was generated in response to a filter request (I mean that FltMgr never actually went and populated the name by itself) and also FltMgr tried really hard not to lose any such name.
First I'd like to point out that FLtMgr caches each name at multiple levels, for each name provider. So if there is no name provider on the stack there is only one entry, for the name at the file system level. If there is a name provider then there are two entries in the cache, one at the name provider's level and one at the file system's level. And so on.
The problem with using stream contexts (or stream handle contexts) is that you can't know what the actual context is until in postCreate. However, it is not uncommon for filters to query for the name during preCreate, which means that FltMgr can't look it up in the cache and so it must generate it every time (which is why querying for names in preCreate is a pretty big performance hit). Moreover, once the name is generated FltMgr can't even cache because it doesn't have the stream. So in this case FltMgr passes the name from preCreate to postCreate in a fashion similar to what a minifilter would do (using the CompletionContext parameter of the preCreate callback). However, since there is no such parameter FltMgr uses the IRP to send some information from preCreate to postCreate, including the name. Once the IRP_MJ_CREATE operation is complete and FltMgr knows what the stream is, it tries to save the name it generated during preCreate into the name cache.
This is where FLT_FILE_NAME_DO_NOT_CACHE comes in. Let's say we have a minifilter that changes the name for a file from C:\foo.txt to C:\bar.txt (so C:\foo.txt is the name seen above the minifilter and C:\bar.txt is the name seen at the file system's level). If the name provider minifilter simply passes the request down to the file system then the file system will see a request for C:\foo.txt. In this case this means that it will be look at the file system level as "C:\foo.txt". Once the name is generated FltMgr will cache it and associate it with the IRP_MJ_CREATE operation. However, once the actual IRP_MJ_CREATE is completed since the C:\foo.txt name is now associated with the IRP FltMgr will cache the name at the file system level as "C:\foo.txt" instead of the "C:\bar.txt" that it should be. This is why name providers must always set the FLT_FILE_NAME_DO_NOT_CACHE flag when making their own requests to the file system, to tell the file system not to cache those names.
So this is the new & improved code (changes in RED):


 NTSTATUS PtGenerateFileNameCallback(  
   __in   PFLT_INSTANCE Instance,  
   __in   PFILE_OBJECT FileObject,  
   __in_opt PFLT_CALLBACK_DATA CallbackData,  
   __in   FLT_FILE_NAME_OPTIONS NameOptions,  
   __out   PBOOLEAN CacheFileNameInformation,  
   __out   PFLT_NAME_CONTROL FileName  
   )  
 {  
   NTSTATUS status = STATUS_SUCCESS;  
   PFLT_FILE_NAME_INFORMATION belowFileName = NULL;  
   PT_DBG_PRINT( PTDBG_TRACE_ROUTINES,  
          ("PassThrough!PtGenerateFileNameCallback: Entered\n") );  
   __try {  

     //
     //  We expect to only get requests for opened, short and normalized names.
     //  If we get something else, fail. 
     //

     if (!FlagOn( NameOptions, FLT_FILE_NAME_OPENED ) && 
 !FlagOn( NameOptions, FLT_FILE_NAME_SHORT ) &&
 !FlagOn( NameOptions, FLT_FILE_NAME_NORMALIZED )) {

         ASSERT(!"we have a received a request for an unknown format. investigate!");

         return STATUS_NOT_SUPPORTED ;
     }

     //  
     // First we need to get the file name. We're going to call   
     // FltGetFileNameInformation below us to get the file name from FltMgr.   
     // However, it is possible that we're called by our own minifilter for   
     // the name so in order to avoid an infinite loop we must make sure to   
     // remove the flag that tells FltMgr to query this same minifilter.   
     //  
     ClearFlag( NameOptions, FLT_FILE_NAME_REQUEST_FROM_CURRENT_PROVIDER );  
     SetFlag( NameOptions, FLT_FILE_NAME_DO_NOT_CACHE );
     //  
     // this will be called for FltGetFileNameInformationUnsafe as well and  
     // in that case we don't have a CallbackData, which changes how we call   
     // into FltMgr.  
     //  
     if (CallbackData == NULL) {  
       //  
       // This must be a call from FltGetFileNameInformationUnsafe.  
       // However, in order to call FltGetFileNameInformationUnsafe the   
       // caller MUST have an open file (assert).  
       //  
       ASSERT( FileObject->FsContext != NULL );  
       status = FltGetFileNameInformationUnsafe( FileObject,  
                            Instance,  
                            NameOptions,  
                            &belowFileName );   
       if (!NT_SUCCESS(status)) {  
         __leave;  
       }                              
     } else {  
       //  
       // We have a callback data, we can just call FltMgr.  
       //  
       status = FltGetFileNameInformation( CallbackData,  
                         NameOptions,  
                         &belowFileName );   
       if (!NT_SUCCESS(status)) {  
         __leave;  
       }                              
     }  
     //  
     // At this point we have a name for the file (the opened name) that   
     // we'd like to return to the caller. We must make sure we have enough   
     // buffer to return the name or we must grow the buffer. This is easy   
     // when using the right FltMgr API.  
     //  
     status = FltCheckAndGrowNameControl( FileName, belowFileName->Name.Length );  
     if (!NT_SUCCESS(status)) {  
       __leave;  
     }  
     //  
     // There is enough buffer, copy the name from our local variable into  
     // the caller provided buffer.  
     //  
     RtlCopyUnicodeString( &FileName->Name, &belowFileName->Name );   
     //  
     // And finally tell the user they can cache this name.  
     //  
     *CacheFileNameInformation = TRUE;  
   } __finally {  
     if ( belowFileName != NULL) {  
       FltReleaseFileNameInformation( belowFileName );        
     }  
   }  
   return status;  
 }