Thursday, September 29, 2011

FILE_OBJECT Names in IRP_MJ_CREATE

I get a lot of questions about what the FILE_OBJECT->FileName is in preCreate or when a legacy filter receives the IRP_MJ_CREATE IRP. This is pretty much then only time when the FILE_OBJECT->FileName is defined (file systems can and do change the name in the FILE_OBJECT sometimes immediately after they process the IRP_MJ_CREATE and so even during postCreate the name is not defined).

So what I'd like to do for this post is to through all the various values that i've seen and what they mean. Please note that these are file system specific and so this list only really applies to the current MS file systems (NTFS, FAT, CDFS, UDFS, ExFAT). Also, this is not necessarily a complete list, it's simply a list of things I've seen (and as such it might also be inaccurate so feel free to comment and mention other cases and such). I certainly plan to update this post whenever I run into new possible values. Please note that FltMgr offers an API (FltGetFileNameInformation()) which will give you the right name in all these circumstances so if you need the file name then use it. This post is not meant to show how to build a name parser but rather to show how the names in IRP_MJ_CREATE work in general so that a minifilter can issue its own creates that take advantage of these features or to help a minifilter identify certain cases without resorting to calling FltGetFileNameInformation() and then parsing the name in all cases (for example when it only cares about volume opens).

Other than FILE_OBJECT->FileName there is another member that is relevant to the name: FILE_OBJECT->RelatedFileObject. This is a pointer to an already opened FILE_OBJECT that the IRP_MJ_CREATE is somehow related to. It is the FILE_OBJECT referenced by the HANDLE that is passed in to the call to the InitializeObjectAttributes() macro in the RootDirectory parameter. Even though the name suggests it, it does not have to be a handle to a directory (as we'll see in the later examples). Also, please note that when I say that RelatedFileObject = "\foo" i don't mean that RelatedFileObject->FileName = "\foo" but rather that RelatedFileObject is pointing to the object "\foo". Since RelatedFileObject is an already opened FILE_OBJECT we can't rely on its FileName member to be relevant.

So anyway, here are the cases:

  • full path (FileObject->FileName = "\directory\file.bin" and RelatedFileObject is NULL). This is one of the most common cases. It simply means the caller is trying to open the file specifying the full path from the volume.
  • relative open (FileObject->FileName = "directory2\file.bin" and RelatedFileObject = "\directory1"). This is also pretty common, the intention is to open the path "\directory1\directory2\file.bin" relative to the a handle the user has for "\directory1". I'm guessing this is the case that gave the OBJECT_ATTRIBUTES->RootDirectory member its name.
  • reopen (FileObject->FileName is empty (Length == 0 and Buffer == NULL) but RelatedFileObject is not null). This is used when the caller wants to open a new handle to an existing FILE_OBJECT. This is not the same as opening a new handle to the existing FILE_OBJECT (duplicating the handle) because the end result of this is to open a new FILE_OBJECT for the same underlying stream, and the two FILE_OBJECTs are not linked in any other way (for example, a filter might want to open its own handle to a user file without bothering to figure out if the user has enough access (if the minifilter might want to write to the file and the original handle didn't allow it then duplicating the handle is more complicated) or without interfering with the additional information stored in the FILE_OBJECT (like the current pointer position) and so on. According to the FASTFAT source code this should work for volume opens as well. This actually happens occasionally so filters should be prepared to deal with it.
  • open an ADS for a file (FileObject->FileName = ":foo:$DATA" and RelatedFileObject is a file or directory). See my previous post on opening alternate data streams for more information on this scenario (why it's useful and so on).
  • opening a volume (both FileObject->FileName and RelatedFileObject are empty (Length == 0, Buffer == NULL)). FltMgr guarantees that even in preCreate the FILE_OBJECT will have the FO_VOLUME_OPEN flag set, but IIRC for a legacy filter that might not be true (in other words the IO manager won't necessarily always set this flag).

So these are pretty much all the cases. There are also some cases where the name needs to be interpreted in a special way and this is indicated by special flags:

  • opening a file by ID (FileObject->FileName is either an 64bit or an 128 bit identifier (Length == 8 or Length == 16 and Buffer should be treated as PVOID)). If I remember correctly the FileName might also be "\" followed by a 64 bit or 128 bit identifier (so the Length is now 10 or 18, respectively). The name should only be interpreted like an ID if the FLT_CALLBACK_DATA->Iopb->Parameters.Create.Options has the FILE_OPEN_BY_FILE_ID flag set. If called in this case FltGetFileNameInformation() will actually open the object specified by the ID and return its name.
  • opening the target of a rename (either full path or relative path). The key thing here is that the object that will actually be opened in the file system is the parent directory of the name specified by FILE_OBJECT->FileName and FILE_OBJECT->RelatedFileObject. In other words, if the full path is "\directory1\directory2\file.bin", then the actual object that will be opened is the directory "\directory1\director2". This is indicated by the SL_OPEN_TARGET_DIRECTORY flag set in FLT_CALLBACK_DATA->Iopb->OperationFlags. See my post on renames for details (http://fsfilters.blogspot.com/2011/06/rename-in-file-system-filters-part-i.html). If called for this case FltGetFileNameInformation() returns the name of the parent directory (if you want the full path including the file then just clear the SL_OPEN_TARGET_DIRECTORY flag before calling FltGetFileNameInformation() and set it back once it returns and you should get the actual name that the file system will receive).

Other than the name one important factor in the preCreate path is whether the user wants to open the path in a case insensitive or a case sensitive fashion. This is specified in FLT_CALLBACK_DATA->Iopb->OperationFlags, check for SL_CASE_SENSITIVE. Of course, once the IRP_MJ_CREATE is completed successfully and a FILE_OBJECT is opened the way to know whether the file was opened with a case sensitive open is to look at FO_OPENED_CASE_SENSITIVE.

One more thing to note is that when calling FltGetFileNameInformation() the name that is returned is a full path (that includes the volume and everything that is necessary to pass it in to an FltCreateFile call and open the stream). The code handles all the cases i mentioned and returns the name of the object that will actually be opened.