Skip to content

Commit e2627b7

Browse files
authored
Merge pull request #46 from pypa/bpo-44497/symlink-infinite-recursion
Avoid symlink infinite loops (separate concerns + repro)
2 parents a0580a3 + d616ed7 commit e2627b7

File tree

2 files changed

+38
-1
lines changed

2 files changed

+38
-1
lines changed

distutils/filelist.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,14 +247,41 @@ def _find_all_simple(path):
247247
"""
248248
Find all files under 'path'
249249
"""
250+
all_unique = _UniqueDirs.filter(os.walk(path, followlinks=True))
250251
results = (
251252
os.path.join(base, file)
252-
for base, dirs, files in os.walk(path, followlinks=True)
253+
for base, dirs, files in all_unique
253254
for file in files
254255
)
255256
return filter(os.path.isfile, results)
256257

257258

259+
class _UniqueDirs(set):
260+
"""
261+
Exclude previously-seen dirs from walk results,
262+
avoiding infinite recursion.
263+
Ref https://bugs.python.org/issue44497.
264+
"""
265+
def __call__(self, walk_item):
266+
"""
267+
Given an item from an os.walk result, determine
268+
if the item represents a unique dir for this instance
269+
and if not, prevent further traversal.
270+
"""
271+
base, dirs, files = walk_item
272+
stat = os.stat(base)
273+
candidate = stat.st_dev, stat.st_ino
274+
found = candidate in self
275+
if found:
276+
del dirs[:]
277+
self.add(candidate)
278+
return not found
279+
280+
@classmethod
281+
def filter(cls, items):
282+
return filter(cls(), items)
283+
284+
258285
def findall(dir=os.curdir):
259286
"""
260287
Find all files under 'dir' and return the list of full filenames.

distutils/tests/test_filelist.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,16 @@ def test_non_local_discovery(self):
331331
expected = [file1]
332332
self.assertEqual(filelist.findall(temp_dir), expected)
333333

334+
@os_helper.skip_unless_symlink
335+
def test_symlink_loop(self):
336+
with os_helper.temp_dir() as temp_dir:
337+
link = os.path.join(temp_dir, 'link-to-parent')
338+
content = os.path.join(temp_dir, 'somefile')
339+
os_helper.create_empty_file(content)
340+
os.symlink('.', link)
341+
files = filelist.findall(temp_dir)
342+
assert len(files) == 1
343+
334344

335345
def test_suite():
336346
return unittest.TestSuite([

0 commit comments

Comments
 (0)