diff options
Diffstat (limited to 'Lib/tempfile.py')
-rw-r--r-- | Lib/tempfile.py | 150 |
1 files changed, 59 insertions, 91 deletions
diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 6bc842f6d8..0537228ba5 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -1,10 +1,10 @@ """Temporary files. This module provides generic, low- and high-level interfaces for -creating temporary files and directories. The interfaces listed -as "safe" just below can be used without fear of race conditions. -Those listed as "unsafe" cannot, and are provided for backward -compatibility only. +creating temporary files and directories. All of the interfaces +provided by this module can be used without fear of race conditions +except for 'mktemp'. 'mktemp' is subject to race conditions and +should not be used; it is provided for backward compatibility only. This module also provides some data items to the user: @@ -27,7 +27,6 @@ __all__ = [ # Imports. -import atexit as _atexit import functools as _functools import warnings as _warnings import io as _io @@ -35,23 +34,7 @@ import os as _os import shutil as _shutil import errno as _errno from random import Random as _Random - -try: - import fcntl as _fcntl -except ImportError: - def _set_cloexec(fd): - pass -else: - def _set_cloexec(fd): - try: - flags = _fcntl.fcntl(fd, _fcntl.F_GETFD, 0) - except OSError: - pass - else: - # flags read successfully, modify - flags |= _fcntl.FD_CLOEXEC - _fcntl.fcntl(fd, _fcntl.F_SETFD, flags) - +import weakref as _weakref try: import _thread @@ -60,8 +43,6 @@ except ImportError: _allocate_lock = _thread.allocate_lock _text_openflags = _os.O_RDWR | _os.O_CREAT | _os.O_EXCL -if hasattr(_os, 'O_NOINHERIT'): - _text_openflags |= _os.O_NOINHERIT if hasattr(_os, 'O_NOFOLLOW'): _text_openflags |= _os.O_NOFOLLOW @@ -90,8 +71,8 @@ else: # Fallback. All we need is something that raises OSError if the # file doesn't exist. def _stat(fn): - f = open(fn) - f.close() + fd = _os.open(fn, _os.O_RDONLY) + _os.close(fd) def _exists(fn): try: @@ -125,7 +106,7 @@ class _RandomNameSequence: def __next__(self): c = self.characters choose = self.rng.choice - letters = [choose(c) for dummy in "123456"] + letters = [choose(c) for dummy in range(8)] return ''.join(letters) def _candidate_tempdir_list(): @@ -167,7 +148,7 @@ def _get_default_tempdir(): for dir in dirlist: if dir != _os.curdir: - dir = _os.path.normcase(_os.path.abspath(dir)) + dir = _os.path.abspath(dir) # Try only a few names per directory. for seq in range(100): name = next(namer) @@ -185,6 +166,13 @@ def _get_default_tempdir(): return dir except FileExistsError: pass + except PermissionError: + # This exception is thrown when a directory with the chosen name + # already exists on windows. + if (_os.name == 'nt' and _os.path.isdir(dir) and + _os.access(dir, _os.W_OK)): + continue + break # no point trying more names in this directory except OSError: break # no point trying more names in this directory raise FileNotFoundError(_errno.ENOENT, @@ -217,14 +205,14 @@ def _mkstemp_inner(dir, pre, suf, flags): file = _os.path.join(dir, pre + name + suf) try: fd = _os.open(file, flags, 0o600) - _set_cloexec(fd) return (fd, _os.path.abspath(file)) except FileExistsError: continue # try again except PermissionError: # This exception is thrown when a directory with the chosen name # already exists on windows. - if _os.name == 'nt': + if (_os.name == 'nt' and _os.path.isdir(dir) and + _os.access(dir, _os.W_OK)): continue else: raise @@ -316,6 +304,14 @@ def mkdtemp(suffix="", prefix=template, dir=None): return file except FileExistsError: continue # try again + except PermissionError: + # This exception is thrown when a directory with the chosen name + # already exists on windows. + if (_os.name == 'nt' and _os.path.isdir(dir) and + _os.access(dir, _os.W_OK)): + continue + else: + raise raise FileExistsError(_errno.EEXIST, "No usable temporary directory name found") @@ -356,8 +352,7 @@ class _TemporaryFileCloser: underlying file object, without adding a __del__ method to the temporary file.""" - # Set here since __del__ checks it - file = None + file = None # Set here since __del__ checks it close_called = False def __init__(self, file, name, delete=True): @@ -378,9 +373,11 @@ class _TemporaryFileCloser: def close(self, unlink=_os.unlink): if not self.close_called and self.file is not None: self.close_called = True - self.file.close() - if self.delete: - unlink(self.name) + try: + self.file.close() + finally: + if self.delete: + unlink(self.name) # Need to ensure the file is deleted on __del__ def __del__(self): @@ -447,7 +444,13 @@ class _TemporaryFileWrapper: # iter() doesn't use __getattr__ to find the __iter__ method def __iter__(self): - return iter(self.file) + # Don't return iter(self.file), but yield from it to avoid closing + # file as long as it's being used as iterator (see issue #23700). We + # can't use 'yield from' here because iter(file) returns the file + # object itself, which has a close method, and thus the file would get + # closed when the generator is finalized, due to PEP380 semantics. + for line in self.file: + yield line def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None, @@ -479,10 +482,14 @@ def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None, flags |= _os.O_TEMPORARY (fd, name) = _mkstemp_inner(dir, prefix, suffix, flags) - file = _io.open(fd, mode, buffering=buffering, - newline=newline, encoding=encoding) + try: + file = _io.open(fd, mode, buffering=buffering, + newline=newline, encoding=encoding) - return _TemporaryFileWrapper(file, name, delete) + return _TemporaryFileWrapper(file, name, delete) + except Exception: + _os.close(fd) + raise if _os.name != 'posix' or _os.sys.platform == 'cygwin': # On non-POSIX and Cygwin systems, assume that we cannot unlink a file @@ -535,7 +542,7 @@ class SpooledTemporaryFile: else: # Setting newline="\n" avoids newline translation; # this is important because otherwise on Windows we'd - # hget double newline translation upon rollover(). + # get double newline translation upon rollover(). self._file = _io.StringIO(newline="\n") self._max_size = max_size self._rolled = False @@ -680,12 +687,17 @@ class TemporaryDirectory(object): in it are removed. """ - # Handle mkdtemp raising an exception - name = None - _closed = False - def __init__(self, suffix="", prefix=template, dir=None): self.name = mkdtemp(suffix, prefix, dir) + self._finalizer = _weakref.finalize( + self, self._cleanup, self.name, + warn_message="Implicitly cleaning up {!r}".format(self)) + + @classmethod + def _cleanup(cls, name, warn_message): + _shutil.rmtree(name) + _warnings.warn(warn_message, ResourceWarning) + def __repr__(self): return "<{} {!r}>".format(self.__class__.__name__, self.name) @@ -693,53 +705,9 @@ class TemporaryDirectory(object): def __enter__(self): return self.name - def cleanup(self, _warn=False, _warnings=_warnings): - if self.name and not self._closed: - try: - _shutil.rmtree(self.name) - except (TypeError, AttributeError) as ex: - if "None" not in '%s' % (ex,): - raise - self._rmtree(self.name) - self._closed = True - if _warn and _warnings.warn: - try: - _warnings.warn("Implicitly cleaning up {!r}".format(self), - ResourceWarning) - except: - if _is_running: - raise - # Don't raise an exception if modules needed for emitting - # a warning are already cleaned in shutdown process. - def __exit__(self, exc, value, tb): self.cleanup() - def __del__(self): - # Issue a ResourceWarning if implicit cleanup needed - self.cleanup(_warn=True) - - def _rmtree(self, path, _OSError=OSError, _sep=_os.path.sep, - _listdir=_os.listdir, _remove=_os.remove, _rmdir=_os.rmdir): - # Essentially a stripped down version of shutil.rmtree. We can't - # use globals because they may be None'ed out at shutdown. - if not isinstance(path, str): - _sep = _sep.encode() - try: - for name in _listdir(path): - fullname = path + _sep + name - try: - _remove(fullname) - except _OSError: - self._rmtree(fullname) - _rmdir(path) - except _OSError: - pass - -_is_running = True - -def _on_shutdown(): - global _is_running - _is_running = False - -_atexit.register(_on_shutdown) + def cleanup(self): + if self._finalizer.detach(): + _shutil.rmtree(self.name) |