[Python-checkins] bpo-38688, shutil.copytree: consume iterator and create list of entries to prevent infinite recursion (GH-17098)
Giampaolo Rodola
webhook-mailer at python.org
Tue Nov 26 20:10:54 EST 2019
https://github.com/python/cpython/commit/9bbcbc9f6dfe1368fe7330b117707f828e6a2c18
commit: 9bbcbc9f6dfe1368fe7330b117707f828e6a2c18
branch: master
author: Bruno P. Kinoshita <kinow at users.noreply.github.com>
committer: Giampaolo Rodola <g.rodola at gmail.com>
date: 2019-11-27T09:10:37+08:00
summary:
bpo-38688, shutil.copytree: consume iterator and create list of entries to prevent infinite recursion (GH-17098)
files:
A Misc/NEWS.d/next/Library/2019-11-22-10-45-03.bpo-38668.iKx23z.rst
M Lib/shutil.py
M Lib/test/test_shutil.py
diff --git a/Lib/shutil.py b/Lib/shutil.py
index f97de788d9d40..8f609b312d331 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -442,7 +442,7 @@ def _ignore_patterns(path, names):
def _copytree(entries, src, dst, symlinks, ignore, copy_function,
ignore_dangling_symlinks, dirs_exist_ok=False):
if ignore is not None:
- ignored_names = ignore(src, set(os.listdir(src)))
+ ignored_names = ignore(src, {x.name for x in entries})
else:
ignored_names = set()
@@ -543,11 +543,12 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
"""
sys.audit("shutil.copytree", src, dst)
- with os.scandir(src) as entries:
- return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
- ignore=ignore, copy_function=copy_function,
- ignore_dangling_symlinks=ignore_dangling_symlinks,
- dirs_exist_ok=dirs_exist_ok)
+ with os.scandir(src) as itr:
+ entries = list(itr)
+ return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
+ ignore=ignore, copy_function=copy_function,
+ ignore_dangling_symlinks=ignore_dangling_symlinks,
+ dirs_exist_ok=dirs_exist_ok)
if hasattr(os.stat_result, 'st_file_attributes'):
# Special handling for directory junctions to make them behave like
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
index dd5589b2e3a5c..460b979ba9332 100644
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -727,6 +727,17 @@ def test_copytree_return_value(self):
rv = shutil.copytree(src_dir, dst_dir)
self.assertEqual(['foo'], os.listdir(rv))
+ def test_copytree_subdirectory(self):
+ # copytree where dst is a subdirectory of src, see Issue 38688
+ base_dir = self.mkdtemp()
+ self.addCleanup(shutil.rmtree, base_dir, ignore_errors=True)
+ src_dir = os.path.join(base_dir, "t", "pg")
+ dst_dir = os.path.join(src_dir, "somevendor", "1.0")
+ os.makedirs(src_dir)
+ src = os.path.join(src_dir, 'pol')
+ write_file(src, 'pol')
+ rv = shutil.copytree(src_dir, dst_dir)
+ self.assertEqual(['pol'], os.listdir(rv))
class TestCopy(BaseTest, unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Library/2019-11-22-10-45-03.bpo-38668.iKx23z.rst b/Misc/NEWS.d/next/Library/2019-11-22-10-45-03.bpo-38668.iKx23z.rst
new file mode 100644
index 0000000000000..28b82ab1619e3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-11-22-10-45-03.bpo-38668.iKx23z.rst
@@ -0,0 +1,5 @@
+Calling func:`shutil.copytree` to copy a directory tree from one directory
+to another subdirectory resulted in an endless loop and a RecursionError. A
+fix was added to consume an iterator and create the list of the entries to
+be copied, avoiding the recursion for newly created directories. Patch by
+Bruno P. Kinoshita.
More information about the Python-checkins
mailing list