IRP_MJ_CLOSE & Shadow File

Hi,

In order to keep coherency in my filter w/ writes, I am utilizing a shadow file. When there are no longer any references to the file (including from the cache manager), meaning that all outstanding writes have been flushed and redirected to my shadow file, I would like to replace the original file with the shadow file to put things back in a consistent state.

I have been reading over the MSDN documentation for IRP_MJ_CLEANUP, IRP_MJ_CLOSE, etc. and I am trying to understand how to best accomplish this. I have a few questions:

  1. From my understanding, IRP_MJ_CLOSE tracks when a particular FILE_OBJECT no longer has any references and is being destroyed by the kernel. However, this does NOT mean that there is no longer any reference to the underlying data, since multiple FILE_OBJECTs can point to the same cache coherency structures. Correct? (This is what I have seen when debugging IRP_MJ_CLOSE).

  2. Assuming 1) is correct, and assuming that the kernel might potentially keep a referenced FILE_OBJECT to data in cache for a very long time, what is the best way to accomplish what I am trying to do?

Here is what I am thinking:

Using a stream context, I can track how many open handles there on a particular stream via IRP_MJ_CREATE. When the number of open user handles reaches zero (tracked via IRP_MJ_CLEANUP) for a particular stream, I can force the cache manager to flush the file data (there by redirecting it to my shadow file). At this point I can proceed with my post-processing.

I’m assuming there are lots of gotchas w/ lock handling which I will figure out, but does this sound about right? Any other piece of advice?

Thanks,
Matt

> 1) From my understanding, IRP_MJ_CLOSE tracks when a particular FILE_OBJECT no longer has

any references and is being destroyed by the kernel.

Yes, MJ_CLOSE is “destructor” (as in C++) for FILE_OBJECT

multiple FILE_OBJECTs can point to the same cache coherency structures. Correct?

Yes.

  1. Assuming 1) is correct, and assuming that the kernel might potentially keep a referenced
    FILE_OBJECT to data in cache for a very long time, what is the best way to accomplish what I am
    trying to do?

CcPurgeCacheSection from your MJ_CLEANUP path.


Maxim S. Shatskih
Windows DDK MVP
xxxxx@storagecraft.com
http://www.storagecraft.com

> Using a stream context, I can track how many open handles there on a

particular stream via IRP_MJ_CREATE.

Only if no filter below you opens the file - at that stage you have no
visibility about what is going on.

When the number of open user handles reaches zero (tracked via
IRP_MJ_CLEANUP) for a particular stream,
I can force the cache manager to flush the file data (there by redirecting
it to my shadow file). At this point I can proceed with my
post-processing.

Yup, but just because you have see the last cleanup doesn’t mean that an
application won’t change the data (for instance if it has a section mapped).
MmCanFileBeTruncate will help you there.

As Maxim says you can try to purge the cache but you have to “know” which
locks you must take (which will change from filesystem to filesystem) and
you have to be able to handle failure. There is a Minor function in
ntddk.h “IRP_MN_FLUSH_AND_PURGE” but it doesn’t have any documentation, I
would guess Vista (or later) and later…

You might want to investigate listening at the ContextCleanupCallBack. In
many cases this means that the file is (finally) being ejected from the
cache (other reasons include you being detached). At that stage there is a
good chance that the file is quiescent and you can do whatever you need to.
Of course there is nothing to say that a new create isn’t in flight, or even
worse, that a lower filter is writing to the file, but it could be that you
don’t need to worry about that.

With that working you can then look at ways of hurrying the file out of the
cache (if you need to) but that is an optimization and so if it fails you
will still be covered.

Rod

Thanks for the tips. I have been going through the FAT FSD sources trying to understand the mechanics of cache interaction a bit better.

One thing I would like to clarify is how CcFlushCache and CcPurgeCacheSection function. I am a bit hazy given the documentation. Here is what I think they do. Please correct me.

CcFlushCache: This routine forces any cached pages to be written to the underlying FSD. This includes writes to memory mapped files, and regular cached writes that would eventually be written by the lazy writer. This routine does not alter any of the section mappings maintained by Mm and Cc.

CcPurgeCacheSection: This route actually causes Cc And Mm to purge and release data section mappings, assuming that the file is not currently mapped (i.e. MmCanFileBeTruncated returns TRUE) at the point that the mappings are being removed.

  1. Does CcPurgeCacheSection cause a flush? From looking at the FAT source it seems not. So wouldn’t I have to call CcFlushCache to do what I want to do above? What happens if you purge without flushing? Do you just lose data?

  2. Do I need to call CcPurgeCacheSection at all? It seems like once I verify that no one has mapped the file, I should be able to flush the file, redirect the output to my shadow file, and rename the shadow file to the original file (overwrite it). Or am I totally off and the rename / delete will fail since the section still exists - and this is why I need to call CcPurgeCacheSection. I’m assuming that CcPurgeCacheSection on the entire file will cause Mm/Cc to release the reference on the FILE_OBJECT which would then probably allow me to delete / rename.

Anyway I am going to try all of this tomorrow but I figured I would ask in parallel.

Thanks,
Matt

> One thing I would like to clarify is how CcFlushCache and CcFlushCache

function. I am a bit hazy given
the documentation. Here is what I think they do. Please correct me.

The first thing to note is that CcFlushCache and CcPurgeCache are not
for use by filters. They are there for Filesystems to take advantage of.
This doesn’t stop people from using them and it “mostly” works, but callers
of
these functions should be aware that Cc Expects you to hold “the right
locks” when they are called and you will eventually deadlock or crash
otherwise.

CcFlushCache: This routine forces any cached pages to be written to the
underlying FSD. This includes
writes to memory mapped files, and regular cached writes that would
eventually be written by the lazy
writer. This routine does not alter any of the section mappings maintained
by Mm and Cc.

Subject to certain restrictions that the FSD may impose, yes. Remember
however you have no guarantee that when this returns someone won’t have
written to a mapped file and dirtied it again. Filters would be better
sending an IRP_MJ_FLUSH IRP

CcPurgeCacheSection: This route actually causes Cc And Mm to purge and
release data section mappings,
assuming that the file is not currently mapped (i.e. MmCanFileBeTruncated
returns TRUE) at the point that
the mappings are being removed.

Yup, note that it can get upset if someone is calling CcInitializeCacheMap
at the same time.

  1. Does CcPurgeCacheSection cause a flush?

No

So wouldn’t I have to call CcFlushCache to do what I want to do above?

Yes you would - and note that you can still lose user data if you get
unlucky with
timing.

What happens if you purge without flushing? Do you just lose data?

yup.

  1. Do I need to call CcPurgeCacheSection at all?

Only you can answer that - you know what you are trying to achieve and when,
although it does seems to me that you don’t have to worry about the purge -
which of course means that you won’t be able to accidently drop user data.

Delete and rename can and will fail, usually for reasons to do with sharing,
its less likely to happen if there
are no unclosed handles left on the file. Watch out for files marked for
delete or delete on close…

Good luck

Rod

I think sending IRP_MJ_FLUSH_BUFFERS is a much better solution. I will deal with the case where there are still memory mappings opened and the synchronization of someone trying to open a new handle to the file in question from above.

Thanks