If a process terminates with a portion of a file locked or closes a file that has outstanding locks, the locks are unlocked by the operating system. However, the time it takes for the operating system to unlock these locks depends upon available system resources. Therefore, it is recommended that your process explicitly unlock all files it has locked when it terminates. If this is not done, access to these files may be denied if the operating system has not yet unlocked them.
There are two interesting things to note about this call.// // Unlock all outstanding file locks. // (VOID) FsRtlFastUnlockAll( &Fcb->Specific.Fcb.FileLock, FileObject, IoGetRequestorProcess( Irp ), NULL );
- First we
can see that a process is passed in (and this is the process associated with
the IRP which FastFat gets from IoGetRequestorProcess()). Moreover, the process
is a mandatory parameter, as we can see from the declaration for FsRtlFastUnlockAll():
NTSTATUS FsRtlFastUnlockAll( __in PFILE_LOCK FileLock, __in PFILE_OBJECT FileObject, __in PEPROCESS ProcessId, __in_opt PVOID Context );The documentation clearly states that the locks that are released are specific to a process and so during IRP_MJ_CLEANUP FastFat will automatically close the handles associated with the handle on which the IRP_MJ_CLEANUP call came. For our example, handle HB. But what about the locks acquired on handle HA ? Are they going to be left behind ?
- The second
interesting thing to note is that the FILE_LOCK structure is a private member
of the FCB, not part of the FSRTL_ADVANCED_FCB_HEADER. So the IO manager can't
know where that structure is located without specific knowledge about each file
system and as such it can't call FsRtlFastUnlockAll by itself.
- On every
lock operation the IO manager sets FILE_OBJECT->LockOperation. It is worth mentioning that LockOperation is never actually used by the file system (at least not that
I've seen in any file system I've looked at).
- When a
handle is closed, if the FILE_OBJECT->LockOperation is set then the IO
manager knows there were some locks taken on the FILE_OBJECT and so it must
release them. So the IO manager will issue the IRP_MJ_LOCK_CONTROL IRP with the
IRP_MN_UNLOCK_ALL minor function (or it will call the FastIO equivalent) to
tell the file system to release all the locks. However, this is not necessary if
this is the last handle for the FILE_OBJECT because the IO manager will issue
the IRP_MJ_CLEANUP IRP in that case and the file system will release all the
locks for that process anyway.
- When a file system processes the IRP_MJ_CLEANUP IRP must also release all the byte
range locks for the FILE_OBJECT for that process.
- A filter
that acquires locks on a FILE_OBJECT without going through the IO manager (i.e.
without calling ZwLockFile() but by issuing their own IO (IRP or FLT_CALLBACK_DATA))
should also set the FILE_OBJECT->LockOperation flag so that the IO manager
knows locks have been taken on that file because otherwise it'll be really complicated to
release the locks at the right time.
- A filter
that duplicates a handle for a FILE_OBJECT might also change the behavior a bit
depending on when it closes the handle. If for example if closes the handle
after the user has closed his handle then the IRP_MJ_CLEANUP IRP will be sent for
their close and not the user's close. Now, the IO manager should handle this
properly and frankly I don't see any problem with it off the top of my head,
but it's something to keep in mind.
- When a
filter calls ZwClose (or FltClose) for a handle they've opened the
IoGetRequestorProcess() call for the IRP_MJ_CLEANUP IRP will return the system
process, so the file system will release all byte range locks on the
FILE_OBJECT in the system process. This might be broken if, for example, there
are two handles, H1 and H2 for the same FILE_OBJECT in the system process and a
lock was taken on handle H1 but then the filter closes H2 and the IO manager
finds FILE_OBJECT->LockOperation set and it tells the file system to release
all the locks in the system process for that FILE_OBJECT and thus it releases
the byte range lock that H1 had.
there are some filters that open their own handles to certain files and then
they forward some requests that arrive on other files to the files they've
opened (for example some back-up filter might forward all IRP_MJ_WRITE for each
file (foo.txt) requests to another file (foo.txt.bak)). Also Shadow File Object type filters will often exhibit the same behavior. Now, if they ever forward a
byte range lock request to the file they've opened (by doing something like
changing the TargetFileObject) then when they close
their file that close will most likely not be in the same process as the
process that requested the byte range lock originally and so some ranges of the
file they've opened might remain locked. In this case the filter might need to
call IRP_MJ_LOCK_CONTROL with IRP_MN_UNLOCK_ALL itself from the process context
where the forwarded lock request originated.