Skip to content

Commit 65c92c5

Browse files
kinowgiampaolo
authored andcommitted
[3.8] bpo-38688, shutil.copytree: consume iterator and create list of entries to prevent infinite recursion (GH-17397)
(cherry picked from commit 9bbcbc9) Co-authored-by: Bruno P. Kinoshita <[email protected]>
1 parent a9c86f5 commit 65c92c5

File tree

3 files changed

+24
-6
lines changed

3 files changed

+24
-6
lines changed

Lib/shutil.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ def _ignore_patterns(path, names):
442442
def _copytree(entries, src, dst, symlinks, ignore, copy_function,
443443
ignore_dangling_symlinks, dirs_exist_ok=False):
444444
if ignore is not None:
445-
ignored_names = ignore(src, set(os.listdir(src)))
445+
ignored_names = ignore(src, {x.name for x in entries})
446446
else:
447447
ignored_names = set()
448448

@@ -543,11 +543,12 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
543543
544544
"""
545545
sys.audit("shutil.copytree", src, dst)
546-
with os.scandir(src) as entries:
547-
return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
548-
ignore=ignore, copy_function=copy_function,
549-
ignore_dangling_symlinks=ignore_dangling_symlinks,
550-
dirs_exist_ok=dirs_exist_ok)
546+
with os.scandir(src) as itr:
547+
entries = list(itr)
548+
return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
549+
ignore=ignore, copy_function=copy_function,
550+
ignore_dangling_symlinks=ignore_dangling_symlinks,
551+
dirs_exist_ok=dirs_exist_ok)
551552

552553
if hasattr(os.stat_result, 'st_file_attributes'):
553554
# Special handling for directory junctions to make them behave like

Lib/test/test_shutil.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,18 @@ def test_copytree_return_value(self):
16051605
rv = shutil.copytree(src_dir, dst_dir)
16061606
self.assertEqual(['foo'], os.listdir(rv))
16071607

1608+
def test_copytree_subdirectory(self):
1609+
# copytree where dst is a subdirectory of src, see Issue 38688
1610+
base_dir = self.mkdtemp()
1611+
self.addCleanup(shutil.rmtree, base_dir, ignore_errors=True)
1612+
src_dir = os.path.join(base_dir, "t", "pg")
1613+
dst_dir = os.path.join(src_dir, "somevendor", "1.0")
1614+
os.makedirs(src_dir)
1615+
src = os.path.join(src_dir, 'pol')
1616+
write_file(src, 'pol')
1617+
rv = shutil.copytree(src_dir, dst_dir)
1618+
self.assertEqual(['pol'], os.listdir(rv))
1619+
16081620

16091621
class TestWhich(unittest.TestCase):
16101622

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Calling func:`shutil.copytree` to copy a directory tree from one directory
2+
to another subdirectory resulted in an endless loop and a RecursionError. A
3+
fix was added to consume an iterator and create the list of the entries to
4+
be copied, avoiding the recursion for newly created directories. Patch by
5+
Bruno P. Kinoshita.

0 commit comments

Comments
 (0)