Skip to content

Commit 5d6b7b1

Browse files
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.
1 parent f6d1f1f commit 5d6b7b1

3 files changed

Lines changed: 63 additions & 7 deletions

File tree

Lib/tempfile.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ def _get_default_tempdir():
166166
return dir
167167
except FileExistsError:
168168
pass
169+
except PermissionError:
170+
# This exception is thrown when a directory with the chosen name
171+
# already exists on windows.
172+
if (_os.name == 'nt' and _os.path.isdir(dir) and
173+
_os.access(dir, _os.W_OK)):
174+
continue
175+
break # no point trying more names in this directory
169176
except OSError:
170177
break # no point trying more names in this directory
171178
raise FileNotFoundError(_errno.ENOENT,
@@ -204,7 +211,8 @@ def _mkstemp_inner(dir, pre, suf, flags):
204211
except PermissionError:
205212
# This exception is thrown when a directory with the chosen name
206213
# already exists on windows.
207-
if _os.name == 'nt':
214+
if (_os.name == 'nt' and _os.path.isdir(dir) and
215+
_os.access(dir, _os.W_OK)):
208216
continue
209217
else:
210218
raise
@@ -296,6 +304,14 @@ def mkdtemp(suffix="", prefix=template, dir=None):
296304
return file
297305
except FileExistsError:
298306
continue # try again
307+
except PermissionError:
308+
# This exception is thrown when a directory with the chosen name
309+
# already exists on windows.
310+
if (_os.name == 'nt' and _os.path.isdir(dir) and
311+
_os.access(dir, _os.W_OK)):
312+
continue
313+
else:
314+
raise
299315

300316
raise FileExistsError(_errno.EEXIST,
301317
"No usable temporary directory name found")

Lib/test/test_tempfile.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,39 @@ def _mock_candidate_names(*names):
274274
lambda: iter(names))
275275

276276

277-
class TestMkstempInner(BaseTestCase):
277+
class TestBadTempdir:
278+
279+
def test_read_only_directory(self):
280+
with _inside_empty_temp_dir():
281+
oldmode = mode = os.stat(tempfile.tempdir).st_mode
282+
mode &= ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
283+
os.chmod(tempfile.tempdir, mode)
284+
try:
285+
if os.access(tempfile.tempdir, os.W_OK):
286+
self.skipTest("can't set the directory read-only")
287+
with self.assertRaises(PermissionError):
288+
self.make_temp()
289+
self.assertEqual(os.listdir(tempfile.tempdir), [])
290+
finally:
291+
os.chmod(tempfile.tempdir, oldmode)
292+
293+
def test_nonexisting_directory(self):
294+
with _inside_empty_temp_dir():
295+
tempdir = os.path.join(tempfile.tempdir, 'nonexistent')
296+
with support.swap_attr(tempfile, 'tempdir', tempdir):
297+
with self.assertRaises(FileNotFoundError):
298+
self.make_temp()
299+
300+
def test_non_directory(self):
301+
with _inside_empty_temp_dir():
302+
tempdir = os.path.join(tempfile.tempdir, 'file')
303+
open(tempdir, 'wb').close()
304+
with support.swap_attr(tempfile, 'tempdir', tempdir):
305+
with self.assertRaises((NotADirectoryError, FileNotFoundError)):
306+
self.make_temp()
307+
308+
309+
class TestMkstempInner(TestBadTempdir, BaseTestCase):
278310
"""Test the internal function _mkstemp_inner."""
279311

280312
class mkstemped:
@@ -389,7 +421,7 @@ def test_textmode(self):
389421
os.lseek(f.fd, 0, os.SEEK_SET)
390422
self.assertEqual(os.read(f.fd, 20), b"blat")
391423

392-
def default_mkstemp_inner(self):
424+
def make_temp(self):
393425
return tempfile._mkstemp_inner(tempfile.gettempdir(),
394426
tempfile.template,
395427
'',
@@ -400,11 +432,11 @@ def test_collision_with_existing_file(self):
400432
# the chosen name already exists
401433
with _inside_empty_temp_dir(), \
402434
_mock_candidate_names('aaa', 'aaa', 'bbb'):
403-
(fd1, name1) = self.default_mkstemp_inner()
435+
(fd1, name1) = self.make_temp()
404436
os.close(fd1)
405437
self.assertTrue(name1.endswith('aaa'))
406438

407-
(fd2, name2) = self.default_mkstemp_inner()
439+
(fd2, name2) = self.make_temp()
408440
os.close(fd2)
409441
self.assertTrue(name2.endswith('bbb'))
410442

@@ -416,7 +448,7 @@ def test_collision_with_existing_directory(self):
416448
dir = tempfile.mkdtemp()
417449
self.assertTrue(dir.endswith('aaa'))
418450

419-
(fd, name) = self.default_mkstemp_inner()
451+
(fd, name) = self.make_temp()
420452
os.close(fd)
421453
self.assertTrue(name.endswith('bbb'))
422454

@@ -528,9 +560,12 @@ def test_choose_directory(self):
528560
os.rmdir(dir)
529561

530562

531-
class TestMkdtemp(BaseTestCase):
563+
class TestMkdtemp(TestBadTempdir, BaseTestCase):
532564
"""Test mkdtemp()."""
533565

566+
def make_temp(self):
567+
return tempfile.mkdtemp()
568+
534569
def do_create(self, dir=None, pre="", suf=""):
535570
if dir is None:
536571
dir = tempfile.gettempdir()

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ Core and Builtins
5353
Library
5454
-------
5555

56+
- Issue #22107: tempfile.gettempdir() and tempfile.mkdtemp() now try again
57+
when a directory with the chosen name already exists on Windows as well as
58+
on Unix. tempfile.mkstemp() now fails early if parent directory is not
59+
valid (not exists or is a file) on Windows.
60+
5661
- Issue #6598: Increased time precision and random number range in
5762
email.utils.make_msgid() to strengthen the uniqueness of the message ID.
5863

0 commit comments

Comments
 (0)