From 9eb0c0e73ec53b8d0e2917c44bcae05a18b56f53 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 20 May 2015 00:10:56 +0300 Subject: 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. --- Lib/tempfile.py | 19 ++++++++++++++---- Lib/test/test_tempfile.py | 50 +++++++++++++++++++++++++++++++++++++++++------ Misc/NEWS | 5 +++++ 3 files changed, 64 insertions(+), 10 deletions(-) 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 @@ def _get_default_tempdir(): _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 @@ def _mkstemp_inner(dir, pre, suf, flags): 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 @@ def mkdtemp(suffix="", prefix=template, dir=None): 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 @@ def _mock_candidate_names(*names): 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 @@ class test__mkstemp_inner(TC): 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 @@ class test__mkstemp_inner(TC): # 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 @@ class test__mkstemp_inner(TC): 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 @@ class test_mkstemp(TC): 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() diff --git a/Misc/NEWS b/Misc/NEWS index 4567a44..2bb7c2d 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -15,6 +15,11 @@ Core and Builtins Library ------- +- 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. + - Issue #6598: Increased time precision and random number range in email.utils.make_msgid() to strengthen the uniqueness of the message ID. -- cgit v0.12