Discussion:
Behavior of FILE_FLAG_DELETE_ON_CLOSE
(too old to reply)
Arno Schoedl
2009-01-31 11:45:35 UTC
Permalink
Hello,

I asked this in a previous thread, but did not get an answer, so I
will open a specific thread for it.

A file opened with FILE_FLAG_DELETE_ON_CLOSE will be deleted after all
handles to it have been released as documented in MSDN.

But I noticed that after closing the _original_ file handle with which
the file has been created, no more new handles to it can be opened.
Can anyone confirm this?

Here is my test code:

//////////////////////////////////
// Demonstration of CreateFile FILE_FLAG_DELETE_ON_CLOSE
behavior
// We must keep the original handle open!
{
TCHAR szFile[MAX_PATH];
APIERR( TempFile( szFile ) );
HANDLE hfileWrite=::CreateFile(
szFile,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
// GetTempFileName creates an empty file
TRUNCATE_EXISTING,
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
FILE_ATTRIBUTE_TEMPORARY |
FILE_FLAG_DELETE_ON_CLOSE,
NULL );
APIERR( hfileWrite!=INVALID_HANDLE_VALUE );


HANDLE hfileRead1=::CreateFile(
szFile,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|
FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
0, // ignored on open
NULL );
APIERR( hfileRead1!=INVALID_HANDLE_VALUE );


APIERR( CloseHandle( hfileWrite ) );


HANDLE hfileRead2=::CreateFile(
szFile,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|
FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
0, // ignored on open
NULL );
APIERRIGNORE( hfileRead2!=INVALID_HANDLE_VALUE,
(ERROR_ACCESS_DENIED) );
// CreateFile fails if the original handle has been
closed, even if
// another handle to the same file is still held.
_ASSERT( hfileRead2==INVALID_HANDLE_VALUE );


APIERR( CloseHandle( hfileRead1 ) );
Alexander Grigoriev
2009-01-31 15:57:03 UTC
Permalink
The file descriptor is marked as delete pending when the file object
(described by the original handle) is destroyed. This is why.
Post by Arno Schoedl
Hello,
I asked this in a previous thread, but did not get an answer, so I
will open a specific thread for it.
A file opened with FILE_FLAG_DELETE_ON_CLOSE will be deleted after all
handles to it have been released as documented in MSDN.
But I noticed that after closing the _original_ file handle with which
the file has been created, no more new handles to it can be opened.
Can anyone confirm this?
//////////////////////////////////
// Demonstration of CreateFile FILE_FLAG_DELETE_ON_CLOSE
behavior
// We must keep the original handle open!
{
TCHAR szFile[MAX_PATH];
APIERR( TempFile( szFile ) );
HANDLE hfileWrite=::CreateFile(
szFile,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
// GetTempFileName creates an empty file
TRUNCATE_EXISTING,
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
FILE_ATTRIBUTE_TEMPORARY |
FILE_FLAG_DELETE_ON_CLOSE,
NULL );
APIERR( hfileWrite!=INVALID_HANDLE_VALUE );
HANDLE hfileRead1=::CreateFile(
szFile,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|
FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
0, // ignored on open
NULL );
APIERR( hfileRead1!=INVALID_HANDLE_VALUE );
APIERR( CloseHandle( hfileWrite ) );
HANDLE hfileRead2=::CreateFile(
szFile,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|
FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
0, // ignored on open
NULL );
APIERRIGNORE( hfileRead2!=INVALID_HANDLE_VALUE,
(ERROR_ACCESS_DENIED) );
// CreateFile fails if the original handle has been
closed, even if
// another handle to the same file is still held.
_ASSERT( hfileRead2==INVALID_HANDLE_VALUE );
APIERR( CloseHandle( hfileRead1 ) );
Kornél Pál
2009-01-31 16:37:16 UTC
Permalink
As Alexander said it is deleted indeed.

Just some more explanation:
I believe that you use FILE_SHARE_DELETE because CreateFile fails the
second time otherwise (didn't try it), but this also means that the file
can be deleted while your second handle is still "open". After you colse
the first handle, your file is deleted so you cannot open it again.

Kornél
Post by Alexander Grigoriev
The file descriptor is marked as delete pending when the file object
(described by the original handle) is destroyed. This is why.
Post by Arno Schoedl
Hello,
I asked this in a previous thread, but did not get an answer, so I
will open a specific thread for it.
A file opened with FILE_FLAG_DELETE_ON_CLOSE will be deleted after all
handles to it have been released as documented in MSDN.
But I noticed that after closing the _original_ file handle with which
the file has been created, no more new handles to it can be opened.
Can anyone confirm this?
//////////////////////////////////
// Demonstration of CreateFile FILE_FLAG_DELETE_ON_CLOSE
behavior
// We must keep the original handle open!
{
TCHAR szFile[MAX_PATH];
APIERR( TempFile( szFile ) );
HANDLE hfileWrite=::CreateFile(
szFile,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
// GetTempFileName creates an empty file
TRUNCATE_EXISTING,
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
FILE_ATTRIBUTE_TEMPORARY |
FILE_FLAG_DELETE_ON_CLOSE,
NULL );
APIERR( hfileWrite!=INVALID_HANDLE_VALUE );
HANDLE hfileRead1=::CreateFile(
szFile,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|
FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
0, // ignored on open
NULL );
APIERR( hfileRead1!=INVALID_HANDLE_VALUE );
APIERR( CloseHandle( hfileWrite ) );
HANDLE hfileRead2=::CreateFile(
szFile,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|
FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
0, // ignored on open
NULL );
APIERRIGNORE( hfileRead2!=INVALID_HANDLE_VALUE,
(ERROR_ACCESS_DENIED) );
// CreateFile fails if the original handle has been
closed, even if
// another handle to the same file is still held.
_ASSERT( hfileRead2==INVALID_HANDLE_VALUE );
APIERR( CloseHandle( hfileRead1 ) );
Arno Schoedl
2009-02-01 09:34:54 UTC
Permalink
Yes, without FLAG_SHARE_DELETE the other CreateFile fails with
ERROR_SHARING_VIOLATION, which is an indication that the behavior is
intended. MSDN says otherwise:
"The file is to be deleted immediately after all of its handles are
closed, which includes the specified handle and _any other open_ or
duplicated handles."
I will leave a comment to MSDN's CreateFile article.
Arno Schoedl
2009-02-01 10:12:29 UTC
Permalink
In fact, the file is not deleted after the original handle is closed.
It stays open and can be read from by other handles that were opened
_before_ the original handle was closed. I just cannot open _new_
handles to this file. Weird.
Alexander Grigoriev
2009-02-01 18:03:58 UTC
Permalink
This is consistent with Unix behavior. The file name is gone when the
DELETE_ON_CLOSE file object is cleaned up (handle is closed). The file data
(inode) will be deleted when the last file is closed.
Post by Arno Schoedl
In fact, the file is not deleted after the original handle is closed.
It stays open and can be read from by other handles that were opened
_before_ the original handle was closed. I just cannot open _new_
handles to this file. Weird.
Corinna Vinschen
2009-02-02 08:27:52 UTC
Permalink
Post by Alexander Grigoriev
This is consistent with Unix behavior. The file name is gone when the
DELETE_ON_CLOSE file object is cleaned up (handle is closed). The file data
(inode) will be deleted when the last file is closed.
Not entirely consistent, unfortunately. In Unix only the inode remains,
not the directory entry. In contrast to Unix, the file is still visible
in the file system as long as an open handle on the file still exists.
Therefore, different to Unix, it's neither possible to create a new file
using the same name in the same directory while the other file still has
an open handle, nor is it possible to delete the parent directory even
if the deleted file is the only remaining file in this dir.


Corinna
--
Corinna Vinschen
Cygwin Project Co-Leader
Red Hat
Kornél Pál
2009-02-01 18:59:13 UTC
Permalink
You should duplicate the original handle rather than opening a new one.
You even can duplicate a handle to the address space of another process.
This way the file will only be closed when you close all the handles and
you will not be affected by FILE_FLAG_DELETE_ON_CLOSE.

Kornél
Post by Arno Schoedl
In fact, the file is not deleted after the original handle is closed.
It stays open and can be read from by other handles that were opened
_before_ the original handle was closed. I just cannot open _new_
handles to this file. Weird.
Arno Schoedl
2009-02-01 21:55:58 UTC
Permalink
The problem is that duplicated handles share each other's file
pointer. Or is there a way other than CreateFile to get a handle with
an independent file pointer?

The whole problem came up in the context of implementing a temporary
IStorage implementation that scales better than the one provided by
StgCreateDocfile. COM Marshalling these objects is a bit tricky
because at the time of marshalling, the destination process is not yet
known. So the marshal data must contain the source process ID and the
handle value. On unmarshalling, the handle is duplicated out of the
source process. But the source process may terminate any time and its
process ID and handle may be reused, so you may end up copying some
random handle.

To solve this, I could create a GUID-named mutex in the source
process, and on unmarshalling, check that it is still there. But it
would be silly to do that if I overlooked a much simpler way to
achieve the same. A GUID-named temporary file seems an obvious
solution, but is useless if you must keep the original handle open.
Kornél Pál
2009-02-02 18:01:01 UTC
Permalink
Hi,

Using FILE_FLAG_DELETE_ON_CLOSE in a process that may terminate at any
time means that the file may be deleted at any time so this is not a
special case for the described sharing problem.

I don't really understand your use case but you should consider to
implement it easier.

To work around the originally described limitation, I suggest you to
duplicate and pass (use two way communication if necessary) the original
handle (opened with FILE_FLAG_DELETE_ON_CLOSE) that keeps the original
file open and will not be deleted (or access denied) untill all
processes using your file terminate. After that you can open a sencond
handle that will not be affected by closing the original handle because
that will not be close and you will have a new file pointer as well.

Kornél
Post by Arno Schoedl
The problem is that duplicated handles share each other's file
pointer. Or is there a way other than CreateFile to get a handle with
an independent file pointer?
The whole problem came up in the context of implementing a temporary
IStorage implementation that scales better than the one provided by
StgCreateDocfile. COM Marshalling these objects is a bit tricky
because at the time of marshalling, the destination process is not yet
known. So the marshal data must contain the source process ID and the
handle value. On unmarshalling, the handle is duplicated out of the
source process. But the source process may terminate any time and its
process ID and handle may be reused, so you may end up copying some
random handle.
To solve this, I could create a GUID-named mutex in the source
process, and on unmarshalling, check that it is still there. But it
would be silly to do that if I overlooked a much simpler way to
achieve the same. A GUID-named temporary file seems an obvious
solution, but is useless if you must keep the original handle open.
Arno Schoedl
2009-02-02 22:36:10 UTC
Permalink
I am effectively doing what you described. The problem is that in
order to support 3rd party interfaces, I must wrap this into a COM
object implementing IMarshal.

IMarshal::MarshalInterface is called on the sender side without
receiver process information. This is something COM does, and that I
have no control over. As implementor, I must provide a BLOB describing
the object. On the receiver side, in the context of the receiving
process or thread, COM passes this BLOB to
IMarshal::UnmarshalInterface.

So in MarshalInterface, I duplicate the original file handle within
the sender process and put the new handle value, the sender process ID
and the temp file path into the BLOB. On the receiver side, I
duplicate the handle from the sender process into the receiver process
with DUPLICATE_CLOSE_SOURCE. For this last DuplicateHandle, I need the
sender process ID, and I must make sure that this process ID is really
the original sender process, not some new process that reuses the
sender process ID. Note that the existence of the file alone does not
guarantee that the sender process is still up. Some 3rd process may
also have received a copy of the temp file, and it may keep the file
open, while the sender already died.

If the source process terminates, unmarshalling fails. This is
definitely better than duplicating some random handle, and without
having destination information while marshaling, I see no better
solution.
Kornél Pál
2009-02-02 23:20:35 UTC
Permalink
Hi,

The handle cannot live without a process that owns it. So you just can't
make this solution work for the case when you want to unmarshal data
after the source process dies. I also don't understand what you want to
do after the source process terminates because you use
FILE_FLAG_DELETE_ON_CLOSE that means that the file will be deleted when
the source process terminates.

Note that if you create a named pipe for example and pass it's name in
the blob then you will be able to communicate with the source process
without process ids.

Kornél
Post by Arno Schoedl
I am effectively doing what you described. The problem is that in
order to support 3rd party interfaces, I must wrap this into a COM
object implementing IMarshal.
IMarshal::MarshalInterface is called on the sender side without
receiver process information. This is something COM does, and that I
have no control over. As implementor, I must provide a BLOB describing
the object. On the receiver side, in the context of the receiving
process or thread, COM passes this BLOB to
IMarshal::UnmarshalInterface.
So in MarshalInterface, I duplicate the original file handle within
the sender process and put the new handle value, the sender process ID
and the temp file path into the BLOB. On the receiver side, I
duplicate the handle from the sender process into the receiver process
with DUPLICATE_CLOSE_SOURCE. For this last DuplicateHandle, I need the
sender process ID, and I must make sure that this process ID is really
the original sender process, not some new process that reuses the
sender process ID. Note that the existence of the file alone does not
guarantee that the sender process is still up. Some 3rd process may
also have received a copy of the temp file, and it may keep the file
open, while the sender already died.
If the source process terminates, unmarshalling fails. This is
definitely better than duplicating some random handle, and without
having destination information while marshaling, I see no better
solution.
Kornél Pál
2009-02-02 23:44:23 UTC
Permalink
I also don't understand your process termination problem because as far
as I know IMarshal::MarshalInterface is called in the source process
then IMarshal::UnmarshalInterface is called in the target process. I
don't see how could this succeed without the source process.
Post by Kornél Pál
Hi,
The handle cannot live without a process that owns it. So you just can't
make this solution work for the case when you want to unmarshal data
after the source process dies. I also don't understand what you want to
do after the source process terminates because you use
FILE_FLAG_DELETE_ON_CLOSE that means that the file will be deleted when
the source process terminates.
Note that if you create a named pipe for example and pass it's name in
the blob then you will be able to communicate with the source process
without process ids.
Kornél
Post by Arno Schoedl
I am effectively doing what you described. The problem is that in
order to support 3rd party interfaces, I must wrap this into a COM
object implementing IMarshal.
IMarshal::MarshalInterface is called on the sender side without
receiver process information. This is something COM does, and that I
have no control over. As implementor, I must provide a BLOB describing
the object. On the receiver side, in the context of the receiving
process or thread, COM passes this BLOB to
IMarshal::UnmarshalInterface.
So in MarshalInterface, I duplicate the original file handle within
the sender process and put the new handle value, the sender process ID
and the temp file path into the BLOB. On the receiver side, I
duplicate the handle from the sender process into the receiver process
with DUPLICATE_CLOSE_SOURCE. For this last DuplicateHandle, I need the
sender process ID, and I must make sure that this process ID is really
the original sender process, not some new process that reuses the
sender process ID. Note that the existence of the file alone does not
guarantee that the sender process is still up. Some 3rd process may
also have received a copy of the temp file, and it may keep the file
open, while the sender already died.
If the source process terminates, unmarshalling fails. This is
definitely better than duplicating some random handle, and without
having destination information while marshaling, I see no better
solution.
Arno Schoedl
2009-02-03 20:17:41 UTC
Permalink
I don't want to make it "work" after the source process dies. I just
want to know that I failed, instead of copying a random handle taking
it, because the file can still be opened, for the original handle. As
we established, this would work for the target process, but sending
the file on to someone else would fail, because the original handle
has been closed. FILE_FLAG_DELETE_ON_CLOSE is not a problem as long as
you keep the original handle alive by duplicating it. The source
process can be long gone. If it passed on the baton to some other
process, and this other process completed its unmarshalling before the
source died, the temporary file lives on.
Arno Schoedl
2009-02-04 11:10:40 UTC
Permalink
That last part about CreateFileMapping I did not understand. Can I
still use FILE_FLAG_DELETE_ON_CLOSE then? This solves the problem of
cleaning up after crashing, which is quite high on the priority list
and difficult to solve otherwise. I don't use memory mapped files
because they consume too much virtual memory, but I could use
CreateFileMapping if the actual access is via regular means.

Do you know the rules of process ID reuse? Is it a theoretical
possiblity after a 2^32 process ID counter wraps around, or is it more
real?
r***@gmail.com
2009-02-04 12:06:57 UTC
Permalink
Post by Arno Schoedl
Do you know the rules of process ID reuse? Is it a theoretical
possiblity after a 2^32 process ID counter wraps around, or is it more
real?
Much more real :-)

One solution is to use GetProcessTimes() and verify the creation time
is unchanged.

Regards,
Roger.
Arno Schoedl
2009-02-04 20:45:38 UTC
Permalink
Post by r***@gmail.com
One solution is to use GetProcessTimes() and verify the creation time
is unchanged.
Thank you, I like this solution! My app is Win2k+, and process ID
reuse within 100 ns is unlikely enough, I suppose. I get around
creating a per-process mutex that as far as I can see would need to be
leaked because there is no simple and reliable way of knowing whether
all marshalled BLOBs have been unmarshalled or released. That would
not be so bad in itself, the process is terminating anyway, but would
trigger leak detection tools.
Kornél Pál
2009-02-04 21:27:57 UTC
Permalink
If you use a single mutex for all the marshals from the same process you
will only leak one object that will be released when the process
terminates. You also could notify the server process of the unmarshal
using interprocess communication. Anyway GetProcessTimes is a much
easier solution.:)

Kornél
Post by Arno Schoedl
Post by r***@gmail.com
One solution is to use GetProcessTimes() and verify the creation time
is unchanged.
Thank you, I like this solution! My app is Win2k+, and process ID
reuse within 100 ns is unlikely enough, I suppose. I get around
creating a per-process mutex that as far as I can see would need to be
leaked because there is no simple and reliable way of knowing whether
all marshalled BLOBs have been unmarshalled or released. That would
not be so bad in itself, the process is terminating anyway, but would
trigger leak detection tools.
Alexander Grigoriev
2009-02-04 16:09:41 UTC
Permalink
If you close file handle after you pass it to CreateFileMapping, the file
(with DELETE_ON_CLOSE) becomes inaccessible and file mapping ceases to work.
This is because the mapping doesn't hold a handle, only a reference to the
file object. File deletion happens when the last handle is closed (in
IRP_MJ_CLEANUP path).
This is supposedly fixed in Vista.
Post by Arno Schoedl
That last part about CreateFileMapping I did not understand. Can I
still use FILE_FLAG_DELETE_ON_CLOSE then? This solves the problem of
cleaning up after crashing, which is quite high on the priority list
and difficult to solve otherwise. I don't use memory mapped files
because they consume too much virtual memory, but I could use
CreateFileMapping if the actual access is via regular means.
Do you know the rules of process ID reuse? Is it a theoretical
possiblity after a 2^32 process ID counter wraps around, or is it more
real?
Kornél Pál
2009-02-04 19:32:05 UTC
Permalink
In this case there is no advantage of creating a file mapping.

You just should create a named mutex with a unique name and pass that
name in the blob. First open the process to ensure that there is no race
condition then check for the mutex. If the mutex exists then you can
assume that the process handle created from the id still belongs to your
source process.

Kornél
Post by Alexander Grigoriev
If you close file handle after you pass it to CreateFileMapping, the file
(with DELETE_ON_CLOSE) becomes inaccessible and file mapping ceases to work.
This is because the mapping doesn't hold a handle, only a reference to the
file object. File deletion happens when the last handle is closed (in
IRP_MJ_CLEANUP path).
This is supposedly fixed in Vista.
Post by Arno Schoedl
That last part about CreateFileMapping I did not understand. Can I
still use FILE_FLAG_DELETE_ON_CLOSE then? This solves the problem of
cleaning up after crashing, which is quite high on the priority list
and difficult to solve otherwise. I don't use memory mapped files
because they consume too much virtual memory, but I could use
CreateFileMapping if the actual access is via regular means.
Do you know the rules of process ID reuse? Is it a theoretical
possiblity after a 2^32 process ID counter wraps around, or is it more
real?
Pavel Lebedinsky [MSFT]
2009-02-04 23:30:08 UTC
Permalink
Post by Alexander Grigoriev
If you close file handle after you pass it to CreateFileMapping, the file
(with DELETE_ON_CLOSE) becomes inaccessible and file mapping ceases to
work. This is because the mapping doesn't hold a handle, only a reference
to the file object. File deletion happens when the last handle is closed
(in IRP_MJ_CLEANUP path).
This is supposedly fixed in Vista.
I believe that Vista behavior in this case should be the same as before -
pages that have already been accessed through the mapped view will
remain accessible until they are trimmed and repurposed. If you access
a page that has not accessed before, or has been repurposed, behavior
will depend on what the filesystem does when Mm tries to read data
from disk. You could get either an inpage exception, or a zeroed page
(this is what currently happens on NTFS).

In other words, doing this produces unpredictable results.
--
Pavel Lebedinsky/Windows Kernel Test
This posting is provided "AS IS" with no warranties, and confers no rights.
Kornél Pál
2009-02-03 23:57:59 UTC
Permalink
OK, now I see that your only problem is that very brief period between
marshaling and unmarshaling. In this case your problem is not related to
at all FILE_FLAG_DELETE_ON_CLOSE.

Also note that the problem was pretty much covered in your "Process ID
lifetime and how to marshal a kernel object handle" thread. (I just
found it using Google:)

I believe that the process id is not likely to be reused so fast but I
understand your problem and you are right that this is possible.

You should create a named object (in the global namespace for Terminal
Services) in the source process with a unique name (on that machine) and
pass that name in the blob (as a Unicode string). If that object still
exists you can make sure that your process also exists.

Alternatively you could communicate using that named object if that's a
named pipe for example.

Also note that CreateFileMapping creates a named object that I believe
fits all your needs. Because the file will remain open once you obtain a
handle to the named file mapping using OpenFileMapping.

This would keep your original handle open and you could open a second
handle of the file like in your original code. Or you could map a view
of the file if you prefer that.

Kornél
Post by Arno Schoedl
I don't want to make it "work" after the source process dies. I just
want to know that I failed, instead of copying a random handle taking
it, because the file can still be opened, for the original handle. As
we established, this would work for the target process, but sending
the file on to someone else would fail, because the original handle
has been closed. FILE_FLAG_DELETE_ON_CLOSE is not a problem as long as
you keep the original handle alive by duplicating it. The source
process can be long gone. If it passed on the baton to some other
process, and this other process completed its unmarshalling before the
source died, the temporary file lives on.
Loading...