Issue #22107: tempfile.gettempdir() and tempfile.mkdtemp() now try again
when a directory with the chosen name already exists on Windows as well as
on Unix. tempfile.mkstemp() now fails early if parent directory is not
valid (not exists or is a file) on Windows.
diff --git a/Lib/tempfile.py b/Lib/tempfile.py
index fbda8eb..184dfc1 100644
--- a/Lib/tempfile.py
+++ b/Lib/tempfile.py
@@ -205,9 +205,14 @@
_os.unlink(filename)
return dir
except (OSError, IOError) as e:
- if e.args[0] != _errno.EEXIST:
- break # no point trying more names in this directory
- pass
+ if e.args[0] == _errno.EEXIST:
+ continue
+ if (_os.name == 'nt' and e.args[0] == _errno.EACCES and
+ _os.path.isdir(dir) and _os.access(dir, _os.W_OK)):
+ # On windows, when a directory with the chosen name already
+ # exists, EACCES error code is returned instead of EEXIST.
+ continue
+ break # no point trying more names in this directory
raise IOError, (_errno.ENOENT,
("No usable temporary directory found in %s" % dirlist))
@@ -242,7 +247,8 @@
except OSError, e:
if e.errno == _errno.EEXIST:
continue # try again
- if _os.name == 'nt' and e.errno == _errno.EACCES:
+ if (_os.name == 'nt' and e.errno == _errno.EACCES and
+ _os.path.isdir(dir) and _os.access(dir, _os.W_OK)):
# On windows, when a directory with the chosen name already
# exists, EACCES error code is returned instead of EEXIST.
continue
@@ -335,6 +341,11 @@
except OSError, e:
if e.errno == _errno.EEXIST:
continue # try again
+ if (_os.name == 'nt' and e.errno == _errno.EACCES and
+ _os.path.isdir(dir) and _os.access(dir, _os.W_OK)):
+ # On windows, when a directory with the chosen name already
+ # exists, EACCES error code is returned instead of EEXIST.
+ continue
raise
raise IOError, (_errno.EEXIST, "No usable temporary directory name found")
diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py
index 465bcda..3d0ac57 100644
--- a/Lib/test/test_tempfile.py
+++ b/Lib/test/test_tempfile.py
@@ -287,7 +287,42 @@
lambda: iter(names))
-class test__mkstemp_inner(TC):
+class TestBadTempdir:
+
+ def test_read_only_directory(self):
+ with _inside_empty_temp_dir():
+ oldmode = mode = os.stat(tempfile.tempdir).st_mode
+ mode &= ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
+ os.chmod(tempfile.tempdir, mode)
+ try:
+ if os.access(tempfile.tempdir, os.W_OK):
+ self.skipTest("can't set the directory read-only")
+ with self.assertRaises(OSError) as cm:
+ self.make_temp()
+ self.assertIn(cm.exception.errno, (errno.EPERM, errno.EACCES))
+ self.assertEqual(os.listdir(tempfile.tempdir), [])
+ finally:
+ os.chmod(tempfile.tempdir, oldmode)
+
+ def test_nonexisting_directory(self):
+ with _inside_empty_temp_dir():
+ tempdir = os.path.join(tempfile.tempdir, 'nonexistent')
+ with support.swap_attr(tempfile, 'tempdir', tempdir):
+ with self.assertRaises(OSError) as cm:
+ self.make_temp()
+ self.assertEqual(cm.exception.errno, errno.ENOENT)
+
+ def test_non_directory(self):
+ with _inside_empty_temp_dir():
+ tempdir = os.path.join(tempfile.tempdir, 'file')
+ open(tempdir, 'wb').close()
+ with support.swap_attr(tempfile, 'tempdir', tempdir):
+ with self.assertRaises(OSError) as cm:
+ self.make_temp()
+ self.assertIn(cm.exception.errno, (errno.ENOTDIR, errno.ENOENT))
+
+
+class test__mkstemp_inner(TestBadTempdir, TC):
"""Test the internal function _mkstemp_inner."""
class mkstemped:
@@ -400,7 +435,7 @@
self.do_create(bin=0).write("blat\n")
# XXX should test that the file really is a text file
- def default_mkstemp_inner(self):
+ def make_temp(self):
return tempfile._mkstemp_inner(tempfile.gettempdir(),
tempfile.template,
'',
@@ -411,11 +446,11 @@
# the chosen name already exists
with _inside_empty_temp_dir(), \
_mock_candidate_names('aaa', 'aaa', 'bbb'):
- (fd1, name1) = self.default_mkstemp_inner()
+ (fd1, name1) = self.make_temp()
os.close(fd1)
self.assertTrue(name1.endswith('aaa'))
- (fd2, name2) = self.default_mkstemp_inner()
+ (fd2, name2) = self.make_temp()
os.close(fd2)
self.assertTrue(name2.endswith('bbb'))
@@ -427,7 +462,7 @@
dir = tempfile.mkdtemp()
self.assertTrue(dir.endswith('aaa'))
- (fd, name) = self.default_mkstemp_inner()
+ (fd, name) = self.make_temp()
os.close(fd)
self.assertTrue(name.endswith('bbb'))
@@ -542,9 +577,12 @@
test_classes.append(test_mkstemp)
-class test_mkdtemp(TC):
+class test_mkdtemp(TestBadTempdir, TC):
"""Test mkdtemp()."""
+ def make_temp(self):
+ return tempfile.mkdtemp()
+
def do_create(self, dir=None, pre="", suf=""):
if dir is None:
dir = tempfile.gettempdir()