summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/tempfile.py28
-rw-r--r--Lib/test/test_tempfile.py11
-rw-r--r--Misc/NEWS.d/next/Library/2021-12-06-22-10-53.bpo-43153.J7mjSy.rst4
3 files changed, 41 insertions, 2 deletions
diff --git a/Lib/tempfile.py b/Lib/tempfile.py
index 2b4f431..55403ad 100644
--- a/Lib/tempfile.py
+++ b/Lib/tempfile.py
@@ -41,6 +41,7 @@ import warnings as _warnings
import io as _io
import os as _os
import shutil as _shutil
+import stat as _stat
import errno as _errno
from random import Random as _Random
import sys as _sys
@@ -889,8 +890,31 @@ class TemporaryDirectory:
try:
_os.unlink(path)
- # PermissionError is raised on FreeBSD for directories
- except (IsADirectoryError, PermissionError):
+ except IsADirectoryError:
+ cls._rmtree(path, ignore_errors=ignore_errors)
+ except PermissionError:
+ # The PermissionError handler was originally added for
+ # FreeBSD in directories, but it seems that it is raised
+ # on Windows too.
+ # bpo-43153: Calling _rmtree again may
+ # raise NotADirectoryError and mask the PermissionError.
+ # So we must re-raise the current PermissionError if
+ # path is not a directory.
+ try:
+ st = _os.lstat(path)
+ except OSError:
+ if ignore_errors:
+ return
+ raise
+ if (_stat.S_ISLNK(st.st_mode) or
+ not _stat.S_ISDIR(st.st_mode) or
+ (hasattr(st, 'st_file_attributes') and
+ st.st_file_attributes & _stat.FILE_ATTRIBUTE_REPARSE_POINT and
+ st.st_reparse_tag == _stat.IO_REPARSE_TAG_MOUNT_POINT)
+ ):
+ if ignore_errors:
+ return
+ raise
cls._rmtree(path, ignore_errors=ignore_errors)
except FileNotFoundError:
pass
diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py
index 1673507..f4aef88 100644
--- a/Lib/test/test_tempfile.py
+++ b/Lib/test/test_tempfile.py
@@ -1641,6 +1641,17 @@ class TestTemporaryDirectory(BaseTestCase):
temp_path.exists(),
f"TemporaryDirectory {temp_path!s} exists after cleanup")
+ @unittest.skipUnless(os.name == "nt", "Only on Windows.")
+ def test_explicit_cleanup_correct_error(self):
+ with tempfile.TemporaryDirectory() as working_dir:
+ temp_dir = self.do_create(dir=working_dir)
+ with open(os.path.join(temp_dir.name, "example.txt"), 'wb'):
+ # Previously raised NotADirectoryError on some OSes
+ # (e.g. Windows). See bpo-43153.
+ with self.assertRaises(PermissionError):
+ temp_dir.cleanup()
+
+
@os_helper.skip_unless_symlink
def test_cleanup_with_symlink_to_a_directory(self):
# cleanup() should not follow symlinks to directories (issue #12464)
diff --git a/Misc/NEWS.d/next/Library/2021-12-06-22-10-53.bpo-43153.J7mjSy.rst b/Misc/NEWS.d/next/Library/2021-12-06-22-10-53.bpo-43153.J7mjSy.rst
new file mode 100644
index 0000000..7800e0a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-12-06-22-10-53.bpo-43153.J7mjSy.rst
@@ -0,0 +1,4 @@
+On Windows, ``tempfile.TemporaryDirectory`` previously masked a
+``PermissionError`` with ``NotADirectoryError`` during directory cleanup. It
+now correctly raises ``PermissionError`` if errors are not ignored. Patch by
+Andrei Kulakov and Ken Jin.