changeset: 96163:63f0ae6e218a branch: 2.7 parent: 96151:6969bac411fa user: Serhiy Storchaka date: Wed May 20 00:10:56 2015 +0300 files: Lib/tempfile.py Lib/test/test_tempfile.py Misc/NEWS description: 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 -r 6969bac411fa -r 63f0ae6e218a Lib/tempfile.py --- a/Lib/tempfile.py Tue May 19 10:09:27 2015 +0300 +++ b/Lib/tempfile.py Wed May 20 00:10:56 2015 +0300 @@ -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 -r 6969bac411fa -r 63f0ae6e218a Lib/test/test_tempfile.py --- a/Lib/test/test_tempfile.py Tue May 19 10:09:27 2015 +0300 +++ b/Lib/test/test_tempfile.py Wed May 20 00:10:56 2015 +0300 @@ -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() diff -r 6969bac411fa -r 63f0ae6e218a Misc/NEWS --- a/Misc/NEWS Tue May 19 10:09:27 2015 +0300 +++ b/Misc/NEWS Wed May 20 00:10:56 2015 +0300 @@ -15,6 +15,11 @@ 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.