Thursday, December 15, 2011

Debugging Minifilters: Using !fltkd.filter

Ever since I started this blog I had planned to go over all the fields that the !fltkd extension can show for the various FltMgr structures and talk about each of them. I'm going to start this with the !fltkd.filter command. Most commands take additional flags that control which information is shown. For this post I'll just focus on the default output without any flags and I'll cover the flags in a different post.

Before I begin I'd like to mention that the !fltkd command in general is heavily dependent on the public symbols for fltmgr.sys and so it doesn't work at all without the right symbols. Moreover, the extension itself and the symbols as well have some bugs and in some cases it can't display things correctly because some of the structures that it expects are not in the public symbols at all or at least they are not under the name the extension expects to find them. So you will occasionally see output from the debugger saying something along the lines of "Your debugger is not using the correct symbols … Type referenced: " followed by a type name. I've removed them from the output in the blog post and we'll work with what we have. Please note that if you want to follow along it's best to use a Win7 machine since the symbols were most complete in that release (though still not 100% fixed).

1: kd> !fltkd.filters

Filter List: 92cddd1c "Frame 0" 
   FLT_FILTER: 94144008 "luafv" "135000"
      FLT_INSTANCE: 94146008 "luafv" "135000"
   FLT_FILTER: 92cdda58 "FileInfo" "45000"
      FLT_INSTANCE: 930e7730 "FileInfo" "45000"
      FLT_INSTANCE: 93448dc8 "FileInfo" "45000"
      FLT_INSTANCE: 9357c340 "FileInfo" "45000"
      FLT_INSTANCE: 93584560 "FileInfo" "45000"
      FLT_INSTANCE: 9359a948 "FileInfo" "45000"
1: kd> !fltkd.filter 94144008 

FLT_FILTER: 94144008 "luafv" "135000"
   FLT_OBJECT: 94144008  [02000000] Filter
      RundownRef               : 0x00000008 (4)
      PointerCount             : 0x00000001 
      PrimaryLink              : [92cdda64-92cddd1c] 
   Frame                    : 92cddcc0 "Frame 0" 
   Flags                    : [00000006] FilteringInitiated NameProvider
   DriverObject             : 941427a8 
   FilterLink               : [92cdda64-92cddd1c] 
   PreVolumeMount           : 81f980cc  luafv!LuafvPreRedirect 
   PostVolumeMount          : 00000000  (null) 
   FilterUnload             : 00000000  (null) 
   InstanceSetup            : 81fa462b  luafv!LuafvInstanceSetup 
   InstanceQueryTeardown    : 00000000  (null) 
   InstanceTeardownStart    : 00000000  (null) 
   InstanceTeardownComplete : 00000000  (null) 
   ActiveOpens              : (941440dc)  mCount=1 
   Communication Port List  : (94144108)  mCount=0 
   Client Port List         : (94144134)  mCount=0 
   VerifierExtension        : 00000000 
   Operations               : 94144164 
   OldDriverUnload          : 00000000  (null) 
   SupportedContexts        : (941440a0)
      VolumeContexts           : (941440a0)
      InstanceContexts         : (941440a0)
      FileContexts             : (941440a0)
      StreamContexts           : (941440a0)
      StreamHandleContexts     : (941440a0)
      TransactionContext       : (941440a0)
   InstanceList             : (94144038)
      FLT_INSTANCE: 94146008 "luafv" "135000"

I'll just go over each field and discuss its meaning.
  • FLT_OBJECT: 94144008 [02000000] Filter - some of the main FltMgr objects (instance, volume and filter) begin with a standard structure that identifies the object type and contains some common synchronization primitives. You can see the structure by doing a "dt fltmgr!_FLT_OBJECT". The FLT_OBJECT is the first member of the structure and as such the address of the FLT_OBJECT is the same as the address of the bigger structure, which is the FLT_FILTER in our case.
  • RundownRef : 0x00000008 (4) - this is a rundown reference primitive that I've mentioned in my post here. The actual reference count is the value in parentheses. This value is incremented and decremented whenever someone calls FltObjectReference() and, respectively, FltObjectDereference(). This allows the filter to wait for other components to release their reference before tearing down (since the rundown reference will enter a special state that doesn't allow more references to be taken once the teardown process starts; this is different from a regular reference count where it's possible that the refcount never drops to 0)..
  • PointerCount : 0x00000001 - this is the actual reference count that controls when the memory is freed. There is no direct way filters can influence this (like FltReferenceObject), but I've seen communication ports increment this count.
  • PrimaryLink : [92cdda64-92cddd1c] - this is a link into a list of filters for the frame. Please remember that a filter is not aware of anything outside the frame and so the list of filters is a per-frame concept.
  • Frame : 92cddcc0 "Frame 0" - this is obviously a pointer to the frame the filter belongs to.
  • Flags : [00000006] FilteringInitiated NameProvider - obviously these are flags. Possible values include (as a general trick it's pretty easy to generate this list by changing the value of the flags in the structure and asking !fltkd.filter to display the filter again):
    • UnloadInProgress - the minifilter is unloading
    • FilteringInitiated - the minifilter has started filtering by calling FltStartFiltering().
    • NameProvider - the minifilter is a name provider. I think FltMgr sets this flag based on whether the minifilter registers the name provider callbacks.
  • DriverObject : 941427a8 - obviously the DRIVER_OBJECT for the filter.
  • FilterLink : [92cdda64-92cddd1c] - this is the same list as the PrimaryLink member of the FLT_OBJECT structure above.
  • PreVolumeMount : 81f980cc luafv!LuafvPreRedirect - this is actually the preOp callback for the IRP_MJ_MOUNT_VOLUME operation (that I've mentioned in my post How File System Filters Attach to Volumes - Part II). This looks like a strange place to have an operation callback but it makes sense because when this callback is called there is no instance associated with the volume (since the volume isn't mounted yet) and so the pointer couldn't have been called through the usual instance mechanism.
  • PostVolumeMount : 00000000 (null) - this is the postOp callback for the IRP_MJ_MOUNT_VOLUME operation.
  • FilterUnload : 00000000 (null) - this is the Unload callback (the FLT_REGISTRATION->FilterUnloadCallback member).
  • InstanceSetup : 81fa462b luafv!LuafvInstanceSetup - this is the FLT_REGISTRATION->InstanceSetupCallback member.
  • InstanceQueryTeardown : 00000000 (null) - this is the FLT_REGISTRATION->InstanceQueryTeardownCallback member.
  • InstanceTeardownStart : 00000000 (null) - this is the FLT_REGISTRATION->InstanceTeardownStartCallback member.
  • InstanceTeardownComplete : 00000000 (null) - this is the FLT_REGISTRATION->InstanceTeardownCompleteCallback member.
  • ActiveOpens : (941440dc) mCount=1 - the value in parentheses is a pointer to an internal FltMgr structure that is simply a doubly linked list protected by a mutex (you can see in the debugger by doing a dt fltmgr!_FLT_MUTEX_LIST_HEAD). For more information on this field please check out my post on Tracking a minifilter's ActiveOpens files.
  • Communication Port List : (94144108) mCount=0 - this is a list of opened communication ports for this filter (the same FLT_MUTEX_LIST_HEAD structure as mentioned above).
  • Client Port List : (94144134) mCount=0 - this is a list of connected communication ports.
  • VerifierExtension : 00000000 - this is the a structure that is used when DriverVerifier is enabled for this minifilter. I will go into more detail on this structure in a future post.
  • Operations : 94144164 - as mentioned in my post Debugging Minifilters: Finding the Callbacks this is actually a pointer to the _FLT_OPERATION_REGISTRATION structure that the filter used when it registered with FltMgr.
  • OldDriverUnload : 00000000 (null) - this is the routine that the driver registered as an unload routine. When a driver registers as a minifilter FltMgr needs to know when the driver is being unloaded (because it is possible to unload a minifilter like any other driver) and so FltMgr replaces the _DRIVER_OBJECT->DriverUnload member function with its own function so that it can detect that someone is trying to unload the driver and so that FltMgr can start the teardown process. Well, the original DriverUnload function is stored in the FLT_FILTER structure in this member.
  • SupportedContexts : (941440a0) - this is an array of information related to the context registration. I will discuss this in more depth in a future post.
  • InstanceList : (94144038) - this is a pointer to another FltMgr internal structure, the fltmgr!_FLT_RESOURCE_LIST_HEAD, which is a list protected by an ERESOURCE (as opposed to the fltmgr!_FLT_MUTEX_LIST_HEAD which was protected by a mutex). The entries in this list are the instances.

That's it for now. Next week I plan to discuss some of the !fltkd.filter flags and their output.

Thursday, December 8, 2011

Debugging Minifilters: Finding the Callbacks

When debugging interop issues I often need to be able to set breakpoints on a certain filter's dispatch function for a certain operation. In most cases the other filter is written by a 3rd party and I have no symbols for it at all and so it's not easy to look at function names and guess what the appropriate function to set the breakpoint on is. So in this post I would like to show how to find the appropriate routines for each operation for both legacy filters and minifilters. Because i'm using Microsoft filters for which symbols are available you can see all the function names, but of course the same principle applies even when you don't have symbols just that the names won't look so pretty.
When dealing with legacy filters the approach to get the callbacks is pretty well-known and it relies on looking at the fields in the DRIVER_OBJECT:
0: kd> !drvobj sr
Driver object (82332290) is for:
 \FileSystem\sr
Driver Extension List: (id , addr)

Device Object list:
8207d830  8213add0  8235e020  8238e248
8238e030  
0: kd> dt 82332290 nt!_DRIVER_OBJECT
   +0x000 Type             : 0n4
   +0x002 Size             : 0n168
   +0x004 DeviceObject     : 0x8207d830 _DEVICE_OBJECT
   +0x008 Flags            : 0x12
   +0x00c DriverStart      : 0xf8489000 Void
   +0x010 DriverSize       : 0x11f00
   +0x014 DriverSection    : 0x823eb998 Void
   +0x018 DriverExtension  : 0x82332338 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING "\FileSystem\sr"
   +0x024 HardwareDatabase : 0x8067d260 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
   +0x028 FastIoDispatch   : 0xf848b4c0 _FAST_IO_DISPATCH
   +0x02c DriverInit       : 0xf8498fd4     long  sr!GsDriverEntry+0
   +0x030 DriverStartIo    : (null) 
   +0x034 DriverUnload     : (null) 
   +0x038 MajorFunction    : [28] 0xf848e726     long  sr!SrCreate+0
0: kd> dps 82332290+0x38 L0n28
823322c8  f848e726 sr!SrCreate
823322cc  f8489428 sr!SrPassThrough
823322d0  f8489428 sr!SrPassThrough
823322d4  f8489428 sr!SrPassThrough
823322d8  f8489320 sr!SrWrite
…
82332330  f8489428 sr!SrPassThrough
82332334  f848dd52 sr!SrPnp
Of course, legacy filters also have a couple of other sets of entrypoints, the FastIO routines and the FS_FILTER_CALLBACKS, which we might need to set breakpoints on:
0: kd> dt 0xf848b4c0 _FAST_IO_DISPATCH
nt!_FAST_IO_DISPATCH
   +0x000 SizeOfFastIoDispatch : 0x70
   +0x004 FastIoCheckIfPossible : 0xf8490914     unsigned char  sr!SrFastIoCheckIfPossible+0
   +0x008 FastIoRead       : 0xf8490962     unsigned char  sr!SrFastIoRead+0
   +0x00c FastIoWrite      : 0xf84909b0     unsigned char  sr!SrFastIoWrite+0
...
   +0x060 FastIoQueryOpen  : 0xf8490f30     unsigned char  sr!SrFastIoQueryOpen+0
   +0x064 ReleaseForModWrite : (null) 
   +0x068 AcquireForCcFlush : (null) 
   +0x06c ReleaseForCcFlush : (null) 

0: kd> dt 0x82332338 nt!_DRIVER_EXTENSION
   +0x000 DriverObject     : 0x82332290 _DRIVER_OBJECT
   +0x004 AddDevice        : (null) 
   +0x008 Count            : 0
   +0x00c ServiceKeyName   : _UNICODE_STRING "sr"
   +0x014 ClientDriverExtension : (null) 
   +0x018 FsFilterCallbacks : 0x8236d238 _FS_FILTER_CALLBACKS
0: kd> dt 0x8236d238 _FS_FILTER_CALLBACKS
nt!_FS_FILTER_CALLBACKS
   +0x000 SizeOfFsFilterCallbacks : 0x38
   +0x004 Reserved         : 0
   +0x008 PreAcquireForSectionSynchronization : 0xf8490c30     long  sr!SrPreAcquireForSectionSynchronization+0
   +0x00c PostAcquireForSectionSynchronization : (null) 
...
   +0x030 PreReleaseForModifiedPageWriter : (null) 
   +0x034 PostReleaseForModifiedPageWriter : (null) 
Things are similar for minifilters. There is a !fltkd command that tries to make things easier, but in my oppinion it could do a better job at helping out. For one, even though the callbacks are defined on a per-filter basis, the !fltkd.filter command that lists the contents of the filter structure doesn't actually show the registered callbacks, and instead one must use the the !fltkd.instance command on one of the instances of the filter , command that has a special flag that can be used to see the callbacks:
1: kd> !fltkd.filters

Filter List: 92cddd1c "Frame 0" 
   FLT_FILTER: 94144008 "luafv" "135000"
      FLT_INSTANCE: 94146008 "luafv" "135000"
   FLT_FILTER: 92cdda58 "FileInfo" "45000"
      FLT_INSTANCE: 930e7730 "FileInfo" "45000"
      FLT_INSTANCE: 93448dc8 "FileInfo" "45000"
      FLT_INSTANCE: 9357c340 "FileInfo" "45000"
      FLT_INSTANCE: 93584560 "FileInfo" "45000"
      FLT_INSTANCE: 9359a948 "FileInfo" "45000"
1: kd> !fltkd.filter 94144008 

FLT_FILTER: 94144008 "luafv" "135000"
   FLT_OBJECT: 94144008  [02000000] Filter
      RundownRef               : 0x00000008 (4)
      PointerCount             : 0x00000001 
      PrimaryLink              : [92cdda64-92cddd1c] 
   Frame                    : 92cddcc0 "Frame 0" 
   Flags                    : [00000006] FilteringInitiated NameProvider
   DriverObject             : 941427a8 
   FilterLink               : [92cdda64-92cddd1c] 
   PreVolumeMount           : 81f980cc  luafv!LuafvPreRedirect 
   PostVolumeMount          : 00000000  (null) 
   FilterUnload             : 00000000  (null) 
   InstanceSetup            : 81fa462b  luafv!LuafvInstanceSetup 
   InstanceQueryTeardown    : 00000000  (null) 
   InstanceTeardownStart    : 00000000  (null) 
   InstanceTeardownComplete : 00000000  (null) 
   ActiveOpens              : (941440dc)  mCount=1 
   Communication Port List  : (94144108)  mCount=0 
   Client Port List         : (94144134)  mCount=0 
   VerifierExtension        : 00000000 
   Operations               : 94144164 
   OldDriverUnload          : 00000000  (null) 
   SupportedContexts        : (941440a0)
      VolumeContexts           : (941440a0)
      InstanceContexts         : (941440a4)
         ALLOCATE_CONTEXT_NODE: 94144a20 "luafv" [01] LookasideList (size=608)
      FileContexts             : (941440a8)
      StreamContexts           : (941440ac)
      StreamHandleContexts     : (941440b0)
         ALLOCATE_CONTEXT_NODE: 94144ae8 "luafv" [01] LookasideList (size=20)
      TransactionContext       : (941440b4)
   InstanceList             : (94144038)
      FLT_INSTANCE: 94146008 "luafv" "135000"
1: kd> !fltkd.instance 94146008 4

FLT_INSTANCE: 94146008 "luafv" "135000"
   CallbackNodes            : (94146054)
       NORMALIZE_NAME_COMPONENT (-22)
         CALLBACK_NODE: 94146584  Inst:(94146008,"luafv","\Device\HarddiskVolume2") "luafv" "135000"
       GENERATE_FILE_NAME (-21)
         CALLBACK_NODE: 9414656c  Inst:(94146008,"luafv","\Device\HarddiskVolume2") "luafv" "135000"
       MDL_WRITE_COMPLETE (-18)
         CALLBACK_NODE: 9414611c  Inst:(94146008,"luafv","\Device\HarddiskVolume2") "luafv" "135000"
       PREPARE_MDL_WRITE (-17)
...
       ACQUIRE_FOR_SECTION_SYNC (-1)
         CALLBACK_NODE: 941462b4  Inst:(94146008,"luafv","\Device\HarddiskVolume2") "luafv" "135000"
       CREATE (0)
         CALLBACK_NODE: 941462cc  Inst:(94146008,"luafv","\Device\HarddiskVolume2") "luafv" "135000"
       CREATE_NAMED_PIPE (1)
         CALLBACK_NODE: 941462e4  Inst:(94146008,"luafv","\Device\HarddiskVolume2") "luafv" "135000"
...
       SET_QUOTA (26)
         CALLBACK_NODE: 9414653c  Inst:(94146008,"luafv","\Device\HarddiskVolume2") "luafv" "135000"
       PNP (27)
         CALLBACK_NODE: 94146554  Inst:(94146008,"luafv","\Device\HarddiskVolume2") "luafv" "135000"
However, the above mentioned debugger function returns pointers to a CALLBACK_NODE structure from which one can figure out what the actual function is. Please note that the structure contains unions and so a lot of the members actually point to the same function. It generally is pretty clear which member should be used depending on the function for which we got the CALLBACK_NODE. For this example I took the IRP_MJ_CREATE callback and the NORMALIZE_NAME_COMPONENT callback:
1: kd> dt 941462cc  fltmgr!_CALLBACK_NODE
   +0x000 CallbackLinks    : _LIST_ENTRY [ 0x93448edc - 0x9343757c ]
   +0x008 Instance         : 0x94146008 _FLT_INSTANCE
   +0x00c PreOperation     : 0x81f9f263     _FLT_PREOP_CALLBACK_STATUS  luafv!LuafvPreCreate+0
   +0x010 PostOperation    : 0x81fa24e8     _FLT_POSTOP_CALLBACK_STATUS  luafv!LuafvPostCreate+0
   +0x00c GenerateFileName : 0x81f9f263     long  luafv!LuafvPreCreate+0
   +0x00c NormalizeNameComponent : 0x81f9f263     long  luafv!LuafvPreCreate+0
   +0x00c NormalizeNameComponentEx : 0x81f9f263     long  luafv!LuafvPreCreate+0
   +0x010 NormalizeContextCleanup : 0x81fa24e8     void  luafv!LuafvPostCreate+0
   +0x014 Flags            : 0 (No matching name)
1: kd> dt 94146584  fltmgr!_CALLBACK_NODE
   +0x000 CallbackLinks    : _LIST_ENTRY [ 0x934374cc - 0x934374cc ]
   +0x008 Instance         : 0x94146008 _FLT_INSTANCE
   +0x00c PreOperation     : 0x81fa09b2     _FLT_PREOP_CALLBACK_STATUS  luafv!LuafvNormalizeNameComponentEx+0
   +0x010 PostOperation    : (null) 
   +0x00c GenerateFileName : 0x81fa09b2     long  luafv!LuafvNormalizeNameComponentEx+0
   +0x00c NormalizeNameComponent : 0x81fa09b2     long  luafv!LuafvNormalizeNameComponentEx+0
   +0x00c NormalizeNameComponentEx : 0x81fa09b2     long  luafv!LuafvNormalizeNameComponentEx+0
   +0x010 NormalizeContextCleanup : (null) 
   +0x014 Flags            : 4 ( CBNFL_USE_NAME_CALLBACK_EX )
Personally I found this approach somewhat cumbersome to use and so what I've done is to list the array of callback directly from the filter object. Please note that the array ends with a record for the IRP_MJ_OPERATION_END pseudo-operation (which is just an array terminator) which has the value of 0x80. This is actually the array of FLT_OPERATION_REGISTRATION structures that is passed to FltRegisterFilter() function in the FLT_REGISTRATION.OperationRegistration field. As such this array does not include the name generation callbacks.
: kd> dt -oca60 94144164 _FLT_OPERATION_REGISTRATION
fltmgr!_FLT_OPERATION_REGISTRATION
[0] @ 94144164 MajorFunction 0xec ''  Flags 0  PreOperation 0x81f980cc  _FLT_PREOP_CALLBACK_STATUS  luafv!LuafvPreRedirect+0  PostOperation (null)   Reserved1 (null)   
[1] @ 94144178 MajorFunction 0xed ''  Flags 0  PreOperation 0x81f980cc  _FLT_PREOP_CALLBACK_STATUS  luafv!LuafvPreRedirect+0  PostOperation (null)   Reserved1 (null)   
[2] @ 9414418c MajorFunction 0xee ''  Flags 0  PreOperation 0x81f98005  _FLT_PREOP_CALLBACK_STATUS  luafv!LuafvPreWrite+0  PostOperation (null)   Reserved1 (null)   
[3] @ 941441a0 MajorFunction 0xef ''  Flags 0  PreOperation 0x81f980cc  _FLT_PREOP_CALLBACK_STATUS  luafv!LuafvPreRedirect+0  PostOperation (null)   Reserved1 (null)   
…
[45] @ 941444e8 MajorFunction 0x19 ''  Flags 0  PreOperation 0x81f980cc  _FLT_PREOP_CALLBACK_STATUS  luafv!LuafvPreRedirect+0  PostOperation (null)   Reserved1 (null)   
[46] @ 941444fc MajorFunction 0x1a ''  Flags 0  PreOperation 0x81f980cc  _FLT_PREOP_CALLBACK_STATUS  luafv!LuafvPreRedirect+0  PostOperation (null)   Reserved1 (null)   
[47] @ 94144510 MajorFunction 0x1b ''  Flags 0  PreOperation 0x81fa3330  _FLT_PREOP_CALLBACK_STATUS  luafv!LuafvPrePnp+0  PostOperation (null)   Reserved1 (null)   
[48] @ 94144524 MajorFunction 0x80 ''  Flags 0  PreOperation (null)   PostOperation (null)   Reserved1 (null)   
...

Thursday, December 1, 2011

Name Normalization in Win8

It's interesting to note that one of the FltMgr features that a lot of minifilters use is the ability to have FltMgr generate normalized paths. This is not something trivial to implement and a lot of the code in FltMgr is dedicated to generating these names. It's also fairly expensive so FltMgr implements a name cache. However file systems don't implement any mechanism to get this information, even though looking at the Win7 WDK we can see some references that indicate that this has been in the works (search for FileNormalizedNameInformation; there are references to it both in wdm.h and ntddk.h):

typedef enum _FILE_INFORMATION_CLASS {
    FileDirectoryInformation         = 1,
    FileFullDirectoryInformation,   // 2
 ...
    FileNormalizedNameInformation,           // 48 <- this seems to be the information class one can use to request the name..
 ...
} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;

//
// This is also used for FileNormalizedNameInformation <- this indicates that the _FILE_NAME_INFORMATION structure can also be used for normalized names
//

typedef struct _FILE_NAME_INFORMATION {
    ULONG FileNameLength;
    WCHAR FileName[1];
} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION;

Well, with the arrival of Win8 things are about to change and file systems and file system filters now have a way to query the file system directly for a normalized name. We can see some of the work that needs to happen by looking at the Win8 FastFat source, but the changes look pretty minimal. Also, there are no changes that I can see in the WDK headers from the Win7 WDK.

_Requires_lock_held_(_Global_critical_region_)    
NTSTATUS
FatCommonQueryInformation (
    IN PIRP_CONTEXT IrpContext,
    IN PIRP Irp
    )
{
...
            case FileNormalizedNameInformation:  <- FastFat will now support this request

                FatQueryNameInfo( IrpContext, Fcb, Ccb, TRUE, Buffer, &Length ); <- we can see a new boolean parameter for FatQueryNameInfo
                break;
}
...

VOID
FatQueryNameInfo (
...
    IN BOOLEAN Normalized,  <- new argument to tell FatQueryNameInfo whether it should return a normalized name or not
...
    )

/*++

Routine Description:

    This routine performs the query name information function for fat.

Arguments:

 ...
    Normalized - if true the caller wants a normalized name (w/out short names).
        This means we're servicing a FileNormalizedNameInformation query.

 ...

Return Value:

    None

--*/

{

 ...
    if (!Normalized &&
        (Fcb->LongName.Unicode.Name.Unicode.Buffer != NULL)) {

        if ((Ccb != NULL) &&
            FlagOn(Ccb->Flags, CCB_FLAG_OPENED_BY_SHORTNAME)) {

            TrimLength = Fcb->FinalNameLength;
        }
    }

 …
}

So now that we know that FastFat implements this, the next logical question is whether NTFS implements it as well and a small program I wrote confirms that it does and that it does indeed return a normalized path like one would expect.

The implications for file system developers are pretty clear, this is one more information class they need to implement and they can use FastFat as a sample of how that can be implemented. One thing I would add is that file systems might want to be extra careful about the performance of the implementation since it's likely that FltMgr's name generation and normalization code will use this information class and so it might be called pretty frequently.

Finally I'd like to talk about the implications about file system filter developers. There are a couple of scenarios that I think could be impacted by this.

  • Name providers must implement this information class. Actually all the filters that currently implement the FileNameInformation class should implement this new class as well (and if they do that they're most likely name providers.. I can't think of a case where a filter would need to implement that class and not be a name provider).
  • Filters that call FltGetFileNameInformation(..,FLT_FILE_NAME_NORMALIZED, ..) in preCreate might not see a significant performance improvement since in preCreate the file isn't opened and so FltMgr can't use this information class and while it might be able to use it for some parts of the path, a lot of the overhead is still there. So querying the normalized name in preCreate is likely to be just as bad as ever.
  • Filters that call FltGetFileNameInformation(..,FLT_FILE_NAME_NORMALIZED, ..) for open files might see some performance improvements since FltMgr should be able to leverage this information class a lot. Please note that I haven't verified that FltMgr actually does use this class, but it would make a lot of sense to use it.
  • Legacy filters would probably benefit the most from this new class but I don't expect that a lot of the legacy filters that are still around are in active development.