From 9bbcbc9f6dfe1368fe7330b117707f828e6a2c18 Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Wed, 27 Nov 2019 14:10:37 +1300 Subject: [PATCH] bpo-38688, shutil.copytree: consume iterator and create list of entries to prevent infinite recursion (GH-17098) --- Lib/shutil.py | 13 +++++++------ Lib/test/test_shutil.py | 11 +++++++++++ .../2019-11-22-10-45-03.bpo-38668.iKx23z.rst | 5 +++++ 3 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-11-22-10-45-03.bpo-38668.iKx23z.rst diff --git a/Lib/shutil.py b/Lib/shutil.py index f97de788d9d..8f609b312d3 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -442,7 +442,7 @@ def ignore_patterns(*patterns): 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 dd5589b2e3a..460b979ba93 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -727,6 +727,17 @@ class TestCopyTree(BaseTest, unittest.TestCase): 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 00000000000..28b82ab1619 --- /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.