classification
Title: tempfile.NamedTemporaryFile not particularly useful on Windows
Type: enhancement Stage: needs patch
Components: Library (Lib), Windows Versions: Python 3.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Carl Osterwisch, Gabi.Davar, John Florian, dabrahams, davide.rizzo, dlenski, eric.smith, eryksun, jason.coombs, jwilk, martin.panter, merwok, ncoghlan, paul.moore, piotr.dobrogost, pitrou, r.david.murray, sbt, steve.dower, tim.golden, zach.ware
Priority: normal Keywords:

Created on 2012-03-10 02:14 by dabrahams, last changed 2017-02-25 05:18 by eryksun.

Files
File name Uploaded Description Edit
ntempfile.py dlenski, 2012-06-30 05:45
share.py sbt, 2012-07-02 13:34
Messages (43)
msg155278 - (view) Author: Dave Abrahams (dabrahams) Date: 2012-03-10 02:14
NamedTemporaryFile is too hard to use portably when you need to open the file by name after writing it.  To do that, you need to close the file first (on Windows), which means you have to pass delete=False, which in turn means that you get no help in cleaning up the actual file resource, which as you can see from the code in tempfile.py is devilishly hard to do correctly.  The fact that it's different on posix (you can open the file for reading by name without closing it first) makes this problem worse. What we really need for this use-case is a way to say, "delete on __del__ but not on close()."
msg155309 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-03-10 13:44
This is quite silly indeed, and is due to the use of O_TEMPORARY in the file creation flags.
msg155316 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2012-03-10 15:03
What's the proposal here? If delete is True, close() must delete the file. It is not acceptable for close() and __del__() to behave differently.

OTOH, if the proposal is merely to change the way the file is opened on Windows so that it can be opened again without closing it first, that sounds fine.
msg155317 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-03-10 15:17
> OTOH, if the proposal is merely to change the way the file is opened
> on Windows so that it can be opened again without closing it first,
> that sounds fine.

That would be my proposal. It probably needs getting rid of O_TEMPORARY,
exposing CreateFile and _open_osfhandle, and using the FILE_SHARE_DELETE
open mode.
msg155333 - (view) Author: Dave Abrahams (dabrahams) Date: 2012-03-10 18:20
I disagree that it's unacceptable for close() and __del__() to behave differently.  The acceptable difference would be that __del__() closes (if necessary) /and/ deletes the file on disk, while close() merely closes the file.

If you can in fact "change the way the file is opened on Windows so that it can be opened again without closing it first," that would be fine with me.  It isn't clear to me that Windows supports that option, but I'm not an expert.

Another possibility, of course, is something like what's implemented in:
https://github.com/dabrahams/zeroinstall/commit/d76de038ef51bd1dae36280f8743e06c7154b44a#L3R44
(an optional argument to close() that prevents deletion).
msg155365 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2012-03-11 02:21
The whole point of close() methods is to offer deterministic resource management to applications that need it. Pointing out to applications when they're relying on CPython's refcounting for prompt resource cleanup is why many of the standard types now trigger ResourceWarning for any application that relies on the GC to clean up such external resources in __del__.

So, no, we're not going to back away from the explicit guarantee in the NamedTemporaryFile docs: "If delete is true (the default), the file is deleted as soon as it is closed." (Especially since doing so would also breach backward compatibility guarantees)

However, you're right that the exclusive read lock in the current implementation makes the default behaviour of NamedTemporaryFile significantly less useful on Windows than it is on POSIX systems, so the implementation should be changed to behave more like the POSIX variant.
msg155374 - (view) Author: Dave Abrahams (dabrahams) Date: 2012-03-11 03:30
If file.close() "offers deterministic resource management," then you have to consider the file's open/closed state to be a resource separate from its existence.  A NamedTemporaryFile whose close() deterministically managed the open/closed state but not the existence of the file would be consistent with file.  That said, I understand the move toward deprecating (in the informal sense) cleanups that rely on GC.

I'm not suggesting breaking backward compatibility, either.  I'm suggesting that it might make sense to allow an explicit close-without-delete as an /extension/ of the current interface.  Given the move away from GC-cleanups, you'd probably want an explicit unlink() method as well in that case.
msg155375 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2012-03-11 03:45
Dave, decoupling the lifecycle of the created file from the object that created it is exactly what delete=False already covers. The complicated dance in NamedTemporaryFile is only to make *__del__* work  a bit more reliably during process shutdown (due to some messy internal problems with what CPython is doing at that point). If you're doing deterministic cleanup (even via atexit), you don't need any of that - you can just use os.unlink().
msg155457 - (view) Author: Dave Abrahams (dabrahams) Date: 2012-03-12 17:59
Nick, not to belabor this, but I guess you don't understand the use-case in question very well, or you'd see that delete=False doesn't cover it.

The use case is this: I have to write a test for a function that takes a filename as a parameter and opens and reads from the file with that name.  The test should conjure up an appropriate file, call the function, check the results, and clean up the file afterwards.  It doesn't matter when the file gets cleaned up, as long as it is cleaned up "eventually."  Having to explicitly delete the file is exactly the kind of boilerplate one wants to avoid in situations like this.

Even if Windows allows a file to be opened for reading (in some circumstances) when it is already open for writing, it isn't hard to imagine that Python might someday have to support an OS that didn't allow it under any circumstances.  It is also a bit perverse to have to keep the file open for writing after you're definitively done writing it, just to prevent it from being deleted prematurely.  I can understand most of the arguments you make against close-without-delete, except those (like the above) that seem to come from a "you shouldn't want that; it's just wrong" stance.
msg157639 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-04-06 02:55
See issue 14514 for an alternate proposal to solve this.  I did search before I opened that issue, but search is currently somewhat broken and I did not find this issue.  I'm not marking it as a dup because my proposal is really a new feature.
msg157925 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2012-04-10 01:29
I agree we need to add something here to better support the idiom where the "close" and "delete" operations on a NamedTemporaryFile are decoupled without the delete becoming a completely independent call to os.unlink().

I agree with RDM's proposal in issue 14514 that the replacement should be "delete on __exit__ but not on close". As with generator context managers, I'd also add in the "last ditch" cleanup behaviour in __del__.

Converting the issue to a feature request for 3.3 - there's no bug here, just an interaction with Windows that makes the existing behavioural options inconvenient.

After all, you can currently get deterministic cleanup (with a __del__ fallback) via:

  @contextmanager
  def named_temp(name):
    f = NamedTemporaryFile(name, delete=False)
    try:
        yield f
    finally:
        try:
            os.unlink(name)
        except OSError:
            pass

You need to be careful to make sure you keep the CM alive (or it will delete the file behind your back), but the idiom RDM described in the other issues handles that for you:

  with named_temp(fname) as f:
     data = "Data\n"
     f.write(data)
     f.close() # Windows compatibility
     with open(fname) as f:
         self.assertEqual(f.read(), data)

As far as the API goes, I'm inclined to make a CM with the above behavour available as a new class method on NamedTemporaryFile:

  with NamedTemporaryFile.delete_after(fname) as f:
      # As per the workaround
msg157927 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2012-04-10 01:33
Although, for the stdlib version, I wouldn't suppress the OS Error (I'd follow what we currently do for TemporaryDirectory)
msg157946 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-04-10 12:18
"delete_after" what? I know it is somewhat implicit in the fact that it is a context manager call, but that is not the only context the method name will be seen in. (eg: 'dir' list of methods, doc index, etc).  Even as a context manager my first thought in reading it was "delete after what?", and then I went, "oh, right".

How about "delete_on_exit"?
msg157947 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-04-10 12:29
By the way, I still think it would be nicer just to have the context manager work as expected with delete=True (ie: doesn't delete until the end of the context manager, whether the file is closed or not).  I'm OK with being voted down on that, though.
msg157948 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-04-10 12:31
> By the way, I still think it would be nicer just to have the context
> manager work as expected with delete=True (ie: doesn't delete until
> the end of the context manager, whether the file is closed or not).
> I'm OK with being voted down on that, though.

Indeed, the current behaviour under Windows seems to be kind of a
nuisance, and having to call a separate method doesn't sound very
user-friendly.
msg157949 - (view) Author: Jason R. Coombs (jason.coombs) * (Python committer) Date: 2012-04-10 13:08
I agree. If the primary usage of the class does not work well on Windows, developers will continue to write code using the primary usage because it works on their unix system, and it will continue to cause failures when run on windows. Because Python should run cross-platform, I consider this a bug in the implementation and would prefer it be adapted such that the primary use case works well on all major platforms.

If there is a separate class method for different behavior, it should be for the specialized behavior, not for the preferred, portable behavior.

I recognize there are backward-compatibility issues here, so maybe it's necessary to deprecate NamedTemporaryFile in favor of a replacement.
msg157952 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-04-10 13:42
Well, fixing NamedTemporaryFile in either of the ways we've discussed isn't going to fix people writing non-portable code.  A unix coder isn't necessarily going to close the file before reading it.  However, it would at least significantly increase the odds that the code would be portable, while the current situation *ensures* that the code is not portable.
msg164358 - (view) Author: Tim Golden (tim.golden) * (Python committer) Date: 2012-06-29 22:01
Daniel. If you have any interest in this issue, would you mind 
summarising the state of affairs, please? I have no direct interest in 
the result but I'm happy to commit a patch or even to work one up if 
somone can come up with a single, concrete suggestion.
msg164369 - (view) Author: Daniel Lenski (dlenski) * Date: 2012-06-30 05:45
Tim Golden,
My preferred solution would be to replace the binary delete argument of the current NamedTemporaryFile implementation with finer-grained options:
   delete=False         # don't delete
   delete=True          # delete after file closed, current behavior
   delete=AFTER_CLOSE   # delete after file closed
   delete=AFTER_CM_EXIT # delete after context manager exits
   delete=AFTER_CM_EXIT_NO_EXCEPTION # delete after CM exit, unless this is due to an exception

I have implemented a Windows-friendly solution to the latter case using Nick Coghlan's code.  My version does not delete the file until the context manager exits, and *if* the context manager exits due to an exception it leaves the file in place and reports its location, to aid me in debugging.
msg164375 - (view) Author: Davide Rizzo (davide.rizzo) * Date: 2012-06-30 08:46
Daniel, Nick, shouldn't the context manager yield f within a with block?
msg164392 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-06-30 16:07
Rather than add a NamedTemporaryFile.delete_after() classmethod, would it not be simpler to just add a close_without_unlink() method to NamedTemporaryFile?

    with NamedTemporaryFile() as f:
        <write to f>
        f.close_without_unlink()
        with open(f.name, 'rb') as f:
            <read from f>
msg164433 - (view) Author: Daniel Lenski (dlenski) * Date: 2012-06-30 23:01
Davide, the @contextlib.contextmanager decorator effectively wraps the
yield statement in the necessary glue so that everything prior to the yield
statement occurs in the __enter__() method of the contextmanager, while
everything subsequent occurs in the __exit__() method.

On Sat, Jun 30, 2012 at 1:46 AM, Davide Rizzo <report@bugs.python.org>wrote:

>
> Davide Rizzo <sorcio@gmail.com> added the comment:
>
> Daniel, Nick, shouldn't the context manager yield f within a with block?
>
> ----------
> nosy: +davide.rizzo
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <http://bugs.python.org/issue14243>
> _______________________________________
>
msg164487 - (view) Author: Daniel Lenski (dlenski) * Date: 2012-07-01 17:19
Richard, I think the problem with this is that it spreads the non-portable
or OS-dependent parts of the code over several places rather than
concentrating them all in one place.

After close_without_unlink(), what would happen when the context manager
exits or when the object is garbage collected?  Would it then get unlinked?

My preference would be to specify the behavior of close/__exit__/GC
operations at the time of the NamedTemporaryFile creation, so that the rest
of the code can be left unchanged.
msg164495 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-07-01 20:30
The webpage

    http://msdn.microsoft.com/en-us/library/aa273350(v=vs.60).aspx

describes the sopen() function which is like open() but has an extra shflag parameter for specifying the sharing allowed.

If sopen() and the associated constants SH_DENYRD, SH_DENYWR, SH_DENYRW and SH_DENYNO were exposed in the os module, then maybe tempfile could use os.sopen() on Windows instead of os.open() to allow the file to be reopened without closing.
msg164496 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-07-01 20:37
> If sopen() and the associated constants SH_DENYRD, SH_DENYWR, SH_DENYRW 
> and SH_DENYNO were exposed in the os module, then maybe tempfile could 
> use os.sopen() on Windows instead of os.open() to allow the file to be 
> reopened without closing.

Sounds like a good way forward.
msg164497 - (view) Author: Tim Golden (tim.golden) * (Python committer) Date: 2012-07-01 20:38
On 01/07/2012 21:37, Antoine Pitrou wrote:
>
> Antoine Pitrou <pitrou@free.fr> added the comment:
>
>> If sopen() and the associated constants SH_DENYRD, SH_DENYWR, SH_DENYRW
>> and SH_DENYNO were exposed in the os module, then maybe tempfile could
>> use os.sopen() on Windows instead of os.open() to allow the file to be
>> reopened without closing.
>
> Sounds like a good way forward.

Agreed. Richard: do you have time to put something together?
I'm happy to try if you don't.
msg164503 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-07-01 22:46
> Agreed. Richard: do you have time to put something together?
> I'm happy to try if you don't.

I'm looking into it.

Unfortunately, it seems that you need to use non-default flags when reopening a shared file.  Eg, if the file is currently opened with SH_DENYNO and O_TEMPORARY, then you must reopen it using SH_DENYNO and O_TEMPORARY.

However, I have an initial implementation of os.sopen() which makes the following work:

  import os, tempfile

  FNAME = "foo.txt"
  DATA = "hello bob"

  def opener(name, flag, mode=0o777):
      return os.sopen(name, flag | os.O_TEMPORARY, os.SH_DENYNO, mode)

  with open(FNAME, "w", opener=opener) as f:
      f.write(DATA)
      f.flush()
      with open(FNAME, "r", opener=opener) as f:
          assert f.read() == DATA

  assert not os.path.exists(FNAME)

BTW, Maybe it would be better to add a keyword-only shareflag argument to os.open() rather than add os.sopen().
msg164504 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-07-01 23:45
I checked the source in

   c:/Program Files (x86)/Microsoft Visual Studio 10.0/VC/crt/src/open.c

and it seems that on Windows open() is more or less implemented as a wrapper of sopen(..., ..., SH_DENYNO, ...).

So the only reason that trying to reopen a NamedTemporaryFile fails on Windows is because when we reopen we need to use O_TEMPORARY.

The following works for unmodified python:

  import os, tempfile

  DATA = b"hello bob"

  def temp_opener(name, flag, mode=0o777):
      return os.open(name, flag | os.O_TEMPORARY, mode)

  with tempfile.NamedTemporaryFile() as f:
      f.write(DATA)
      f.flush()
      with open(f.name, "rb", opener=temp_opener) as f:
          assert f.read() == DATA

  assert not os.path.exists(f.name)

So maybe we should just define tempfile.opener().
msg164505 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2012-07-02 00:45
Alternatively, perhaps it would make sense to have a "reopen()" method on file objects that covers the necessary dance to reopen with the correct flags?

That would solve more problems than just this one (possibly including making it possible to "reopen" StringIO and BytesIO objects).
msg164509 - (view) Author: Tim Golden (tim.golden) * (Python committer) Date: 2012-07-02 08:53
On 30/06/2012 06:45, Daniel Lenski wrote:
> My preferred solution would be to replace the binary delete argument of the current NamedTemporaryFile implementation with finer-grained options:
>    delete=False         # don't delete
>    delete=True          # delete after file closed, current behavior
>    delete=AFTER_CLOSE   # delete after file closed
>    delete=AFTER_CM_EXIT # delete after context manager exits
>    delete=AFTER_CM_EXIT_NO_EXCEPTION # delete after CM exit, unless this is due to an exception

I'm aware that Richard & others are fleshing out alternatives. But my
having asked you to propose something I wanted to come back on this
particular suggestion. I think it's just too complex an API. Not least
because, on Windows, we're making use of a filesystem feature which will
delete on closure regardless (so the implementation on Windows skips the
context-based delete).

I'm not sure what we'll end up with but I'm more inclined towards the
sort of method-based closer/reopener which is more explicit.
msg164514 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-07-02 13:34
I wrote in an earlier message that a file opened with O_TEMPORARY must be reopened with O_TEMPORARY.  This is not quite accurate.

Using O_TEMPORARY causes the FILE_SHARE_DELETE sharing mode to be used, and a file currently opened with FILE_SHARE_DELETE can only be reopened with FILE_SHARE_DELETE.

Unfortunately using O_TEMPORARY is the only way allowed by msvcrt to get FILE_SHARE_DELETE, even though it also has the orthogonal effect of unlinking the file when all handles are closed.

The nice thing about FILE_SHARE_DELETE is that it gives Unix-like behaviour: the file can be renamed or deleted while you have an open handle, and you can still continue to use the handle.

Attached is a mostly untested attempt at writing replacements for open() and os.open() which use the FILE_SHARE_DELETE sharing mode.  Among other things, these can be used for reopening temporary files.

Even if tempfile does not use make use of this, I think something similar would be useful in the stdlib.
msg164617 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-07-03 19:22
I have opened Issue #15244 with a patch to add a share module to the stdlib.

After monkey patching builtins.open(), io.open() and os.open() to be equivalents using FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, the regression test suite still runs successfully.
msg184053 - (view) Author: Piotr Dobrogost (piotr.dobrogost) Date: 2013-03-12 22:02
@sbt

> (...) and it seems that on Windows open() is more or less implemented
> as a wrapper of sopen(..., ..., SH_DENYNO, ...).

> So the only reason that trying to reopen a NamedTemporaryFile fails on
> Windows is because when we reopen we need to use O_TEMPORARY.

Could you elaborate on this? What's the relation between SH_DENYNO argument to sopen() and O_TEMPORARY flag?
msg184056 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2013-03-12 22:49
Sorry, I was not very clear.

If you use the O_TEMPORARY flag with open() to get a file handle, then the share mode used with the underlying CreateFile() function is 

    X = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE

whereas, if you don't use O_TEMPORARY then the share mode is

    Y = FILE_SHARE_READ | FILE_SHARE_WRITE

While a handle is open with share mode X, you can only reopen the file if you also use share mode X.  Therefore (using the msvcrt) you can only reopen it using O_TEMPORARY.*


* sopen() does give some extra control over the share mode, but you still can't use it to get share mode X without also using O_TEMPORARY.
msg184088 - (view) Author: Piotr Dobrogost (piotr.dobrogost) Date: 2013-03-13 15:40
@sbt

Thanks for info. Also you mentioned looking at c:/Program Files (x86)/Microsoft Visual Studio 10.0/VC/crt/src/open.c What version of Visual Studio/SDK this file is available in?

Also I'd like to point out that this problem came up at Stack Overflow in question "How to create a temporary file that can be read by a subprocess?" (http://stackoverflow.com/q/15169101/95735)
msg228371 - (view) Author: Mark Lawrence (BreamoreBoy) * Date: 2014-10-03 20:36
This is one of several Windows file related issues, see also #12939, #21859, #15244 and possibly others.  How can we take this forward? If it's of any use I can help with testing as I've a new Windows 8.1 HP laptop with 8G of memory.
msg244762 - (view) Author: Carl Osterwisch (Carl Osterwisch) Date: 2015-06-03 15:29
I need to have an external DLL open and read the named file but this current NamedTemporaryFile implementation prevents that from working on Windows.
msg288473 - (view) Author: John Florian (John Florian) Date: 2017-02-23 19:44
I just stumbled onto this though I'm not writing for Windows.  Instead, I'm on Fedora 25 with Python 3.5.2 and I went nearly crazy tracing down what seemed to be inconsistent behavior.  My use case has Python using NamedTemporaryFile(delete=True) in a CM to produce content fed into a subprocess.  The code had been reliably working and then it just didn't.  The only thing changing was the content being written, an rendered Jinja2 template.  I believe the fate is determined by the content length.  In debugging another problem, I'd been trivializing the template and once it got down to about 3k (rendered) the subprocess began seeing a file whose length was 0 bytes.  Make the template bigger and all works again.  Calling close() resolves the issue, but of course requires delete=False which removed much of the value of this object.  Preliminary testing looks like flush() may also resolve the issue.

Have I just been naive and getting lucky all along because this is expected or is there something else fishy here worth investigation?
msg288504 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2017-02-24 07:04
John, your problem sounds different - if you're opening the files in binary mode, then you'll be getting a default buffer that's probably 4k or 8k in size, so if you're writing less content than that, the subprocess won't see anything until you explicitly flush() the buffer to disk (and even if you're writing more than that, the subprocess may see a truncated version without an explicit flush()). 

By contrast, the issue here relates to the fact that on Windows it's currently necessary to do the following in order to get multiple handles to a NamedTemporaryFile:

1. Open the file in the current process
2. Write content to the file
3*. Close the file in the current process
4. Open the file by name in another process (or the current process)
5. Read content from the file
6. Close the second file handle
7*. Delete the file

*On POSIX systems, you can just skip step 3 and leave closing the original file handle until step 7, and that's the design that the current NamedTemporaryFile is built around. Most of the discussion above is about figuring out how to make that same approach "just work" on Windows (perhaps with some additional flags used in step 4), rather than  providing a third option beyond the current delete-on-close and delete-manually.
msg288520 - (view) Author: John Florian (John Florian) Date: 2017-02-24 12:21
Okay Nick. Thanks for the detailed info. I suspected buffering was a factor, but wasn't certain. Would it be worthwhile pursuing a note in the docs or would that constitute clutter over what should be a standard assumption? I was thrown off course for all the prior uses without issues, but in hindsight I don't know offhand how many involved a subprocess.
msg288521 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2017-02-24 12:28
John: I don't think it would be clutter to have an explicit reminder about that point in the NamedTemporaryFile documentation, so feel free to file a separate enhancement issue for it.
msg288538 - (view) Author: Eryk Sun (eryksun) * Date: 2017-02-25 04:49
Nick wrote:
> 1. Open the file in the current process
> 2. Write content to the file
> 3*. Close the file in the current process

In step 1, do you mean calling NamedTemporaryFile with delete=False? In that case there's no immediate problem with opening the file again in Windows. If you mean calling NamedTemporaryFile with delete=True, then step 3 deletes the file. 

Adding support for Windows share modes would be useful in general and would help within the current process. However, users may also need to open the temporary file in another process. Most programs don't open their files with shared delete access. 

There's a workaround to allow the file to be opened normally, but it involves setting the delete disposition and then clearing it in a pointless dance. It would be better to implement an option such as delete=AFTER_CM_EXIT, to try to remove the file without relying on O_TEMPORARY. The downside is that the file won't be deleted if the interpreter crashes, gets terminated or if another process has the file open without delete sharing.
msg288540 - (view) Author: Eryk Sun (eryksun) * Date: 2017-02-25 05:18
Richard wrote:
> while a handle is open with share mode X, you can only reopen 
> the file if you also use share mode X

To clarify, the share mode is not a property of a handle. It's a property of a File object. A handle is a generic reference to any kind of kernel object, not just a File.

The following is a brief discussion about access sharing and the way file deletion works in Windows. Both of these tend to frustrate Unix programmers who end up supporting Windows. In this discussion, a "File", with an uppercase 'F', is the Windows kernel object type that references an open device or file-system directory, file, or stream. A "file", with a lowercase 'f', is a data file.

Shared access is implemented for File objects [1] and tracked in a SHARE_ACCESS record. When opening a file or directory, its shared access state is updated by the kernel function IoCheckShareAccess [2]. Discretionary shared access is primarily a concern for file systems, but volume devices and disk devices (e.g. \\.\C: and \\.\PhysicalDrive0) also use it. Devices that are flagged for mandatory exclusive access [3] (e.g. \\.\COM1) generally ignore the share mode. Some non-exclusive devices also ignore the share mode (e.g. \\.NUL and \\.CON). 

If it's not ignored, the share mode affects delete, write, read, and execute access. The following File access rights are affected: DELETE, FILE_WRITE_DATA, FILE_APPEND_DATA, FILE_READ_DATA, and FILE_EXECUTE. The share mode thus affects any combination of generic access -- GENERIC_ALL, GENERIC_WRITE, GENERIC_READ, GENERIC_EXECUTE. 

A File object's requested sharing is stored in its SharedDelete, SharedWrite, and SharedRead members. The granted access that's relevant to the share mode is stored in the DeleteAccess, WriteAccess (write/append), and ReadAccess (read/execute) members. Given these values, checking for a sharing violation and updating the shared access counts uses the following logic:

    RequireSharedDelete = DeleteAccessCount > 0;
    RequireSharedWrite = WriteAccessCount > 0;
    RequireSharedRead = ReadAccessCount > 0;
    DenyDeleteAccess = SharedDeleteCount < OpenCount;
    DenyWriteAccess = SharedWriteCount < OpenCount;
    DenyReadAccess = SharedReadCount < OpenCount;
    
    if (RequireSharedDelete && !SharedDelete ||
        RequireSharedWrite && !SharedWrite ||
        RequireSharedRead && !SharedRead ||
        DenyDeleteAccess && DeleteAccess ||
        DenyWriteAccess && WriteAccess ||
        DenyReadAccess && ReadAccess)
    {
        return STATUS_SHARING_VIOLATION;
    }

    OpenCount++;
    DeleteAccessCount += DeleteAccess;
    WriteAccessCount += WriteAccess;
    ReadAccessCount += ReadAccess;
    SharedDeleteCount += SharedDelete;
    SharedWriteCount += SharedWrite;
    SharedReadCount += SharedRead;

For example, to be granted delete access, all existing File object references must share delete access. However, if a file is opened with delete sharing but delete access hasn't been granted, then it can be opened again without delete sharing.

The SHARE_ACCESS structure is usually stored in a file (or stream) control block (FCB/SCB), which is a structure that coordinates access to a file or directory across multiple File objects. The FsContext member of a File object points at the FCB. 

A file system stores its private state for a File in a context control block (CCB), to which the File's FsContext2 member points. The CCB is where a file system tracks, for example, whether the file should be deleted when the object is closed. 

Deleting a file sets a delete disposition in the FCB/SCB (or LCB if hard links are supported). The file can't be unlinked until all referencing File objects have been closed and the underlying FCB/SCB/LCB is closing. Until then a 'deleted' file is still linked in the parent directory and prevents the directory from being deleted.

A deleted but still referenced file is in a semi-zombie state. Windows file systems don't allow opening such a file for any access, but it can still be accessed via existing objects. If one of these File objects has delete access, it can be used to unset the delete disposition (e.g. via SetFileInformationByHandle) to make the file accessible again.

[1]: https://msdn.microsoft.com/en-us/library/ff545834
[2]: https://msdn.microsoft.com/en-us/library/ff548341
[3]: https://msdn.microsoft.com/en-us/library/ff563827
History
Date User Action Args
2017-02-25 05:18:30eryksunsetmessages: + msg288540
2017-02-25 04:49:40eryksunsetnosy: + eryksun

messages: + msg288538
versions: + Python 3.7, - Python 3.5
2017-02-24 12:28:05ncoghlansetmessages: + msg288521
2017-02-24 12:21:20John Floriansetmessages: + msg288520
2017-02-24 07:04:09ncoghlansetmessages: + msg288504
2017-02-24 03:07:26BreamoreBoysetnosy: - BreamoreBoy
2017-02-23 22:12:10jwilksetnosy: + jwilk
2017-02-23 19:44:46John Floriansetnosy: + John Florian
messages: + msg288473
2015-06-03 15:29:03Carl Osterwischsetnosy: + Carl Osterwisch
messages: + msg244762
2015-03-30 13:54:54paul.mooresetnosy: + paul.moore
2014-10-04 23:58:25martin.pantersetnosy: + martin.panter
2014-10-03 20:36:43BreamoreBoysetnosy: + BreamoreBoy, zach.ware, steve.dower, - brian.curtin

messages: + msg228371
versions: + Python 3.5, - Python 3.4
2013-08-13 14:39:33Gabi.Davarsetnosy: + Gabi.Davar
2013-03-13 15:40:26piotr.dobrogostsetmessages: + msg184088
2013-03-12 22:49:11sbtsetmessages: + msg184056
2013-03-12 22:02:32piotr.dobrogostsetmessages: + msg184053
2013-03-05 22:07:52piotr.dobrogostsetnosy: + piotr.dobrogost
2012-07-03 19:22:32sbtsetmessages: + msg164617
2012-07-02 13:34:36sbtsetfiles: + share.py

messages: + msg164514
2012-07-02 08:53:38tim.goldensetmessages: + msg164509
title: tempfile.NamedTemporaryFile not particularly useful on Windows -> tempfile.NamedTemporaryFile not particularly useful on Windows
2012-07-02 00:45:18ncoghlansetmessages: + msg164505
2012-07-01 23:45:48sbtsetmessages: + msg164504
2012-07-01 22:46:14sbtsetmessages: + msg164503
2012-07-01 20:38:59tim.goldensetmessages: + msg164497
2012-07-01 20:37:10pitrousetstage: needs patch
messages: + msg164496
components: + Windows
versions: + Python 3.4, - Python 3.3
2012-07-01 20:30:37sbtsetmessages: + msg164495
2012-07-01 17:19:35dlenskisetmessages: + msg164487
2012-06-30 23:01:52dlenskisetmessages: + msg164433
title: tempfile.NamedTemporaryFile not particularly useful on Windows -> tempfile.NamedTemporaryFile not particularly useful on Windows
2012-06-30 16:07:30sbtsetnosy: + sbt
messages: + msg164392
2012-06-30 08:46:28davide.rizzosetnosy: + davide.rizzo
messages: + msg164375
2012-06-30 05:45:37dlenskisetfiles: + ntempfile.py

messages: + msg164369
2012-06-29 22:01:11tim.goldensetmessages: + msg164358
2012-06-29 21:28:52dlenskisetnosy: + dlenski
2012-04-10 13:42:46r.david.murraysetmessages: + msg157952
2012-04-10 13:08:58jason.coombssetmessages: + msg157949
title: tempfile.NamedTemporaryFile not particularly useful on Windows -> tempfile.NamedTemporaryFile not particularly useful on Windows
2012-04-10 12:31:30pitrousetmessages: + msg157948
2012-04-10 12:29:46r.david.murraysetmessages: + msg157947
2012-04-10 12:18:29r.david.murraysetmessages: + msg157946
2012-04-10 01:33:23ncoghlansetmessages: + msg157927
2012-04-10 01:31:35ncoghlanlinkissue14514 superseder
2012-04-10 01:29:28ncoghlansettype: behavior -> enhancement
title: NamedTemporaryFile unusable under Windows -> tempfile.NamedTemporaryFile not particularly useful on Windows
messages: + msg157925
versions: - Python 2.7, Python 3.2
2012-04-06 02:55:39r.david.murraysetnosy: + r.david.murray
messages: + msg157639
2012-03-23 15:51:18jason.coombssetnosy: + jason.coombs
2012-03-12 17:59:58merwoksetnosy: + merwok
2012-03-12 17:59:52dabrahamssetmessages: + msg155457
2012-03-11 03:45:33ncoghlansetmessages: + msg155375
2012-03-11 03:30:15dabrahamssetmessages: + msg155374
2012-03-11 02:21:04ncoghlansetmessages: + msg155365
2012-03-10 18:20:17dabrahamssetmessages: + msg155333
2012-03-10 15:20:07pitrousetnosy: + tim.golden, brian.curtin
2012-03-10 15:17:31pitrousetmessages: + msg155317
2012-03-10 15:03:00ncoghlansetmessages: + msg155316
2012-03-10 13:44:25pitrousetnosy: + ncoghlan, pitrou
title: NamedTemporaryFile usability request -> NamedTemporaryFile unusable under Windows
messages: + msg155309

versions: + Python 3.2, Python 3.3
type: behavior
2012-03-10 04:53:46eric.smithsetnosy: + eric.smith
2012-03-10 02:14:08dabrahamscreate