Skip to content

Commit 4e83468

Browse files
committed
Add has_recursive_symlinks function
1 parent b64c496 commit 4e83468

File tree

2 files changed

+60
-0
lines changed

2 files changed

+60
-0
lines changed

easybuild/tools/filetools.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import hashlib
4545
import imp
4646
import inspect
47+
import itertools
4748
import os
4849
import re
4950
import shutil
@@ -2340,6 +2341,25 @@ def copy_files(paths, target_path, force_in_dry_run=False, target_single_file=Fa
23402341
raise EasyBuildError("One or more files to copy should be specified!")
23412342

23422343

2344+
def has_recursive_symlinks(path):
2345+
"""
2346+
Check the given directory for recursive symlinks.
2347+
2348+
That means symlinks to folders inside the path which would cause infinite loops when traversed regularily.
2349+
2350+
:param path: Path to directory to check
2351+
"""
2352+
for dirpath, dirnames, filenames in os.walk(path, followlinks=True):
2353+
for name in itertools.chain(dirnames, filenames):
2354+
fullpath = os.path.join(dirpath, name)
2355+
if os.path.islink(fullpath):
2356+
linkpath = os.path.realpath(fullpath)
2357+
fullpath += os.sep # To catch the case where both are equal
2358+
if fullpath.startswith(linkpath + os.sep):
2359+
return True
2360+
return False
2361+
2362+
23432363
def copy_dir(path, target_path, force_in_dry_run=False, dirs_exist_ok=False, **kwargs):
23442364
"""
23452365
Copy a directory from specified location to specified location

test/framework/filetools.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,6 +1790,46 @@ def test_copy_files(self):
17901790
regex = re.compile("^copied 2 files to .*/target")
17911791
self.assertTrue(regex.match(stdout), "Pattern '%s' should be found in: %s" % (regex.pattern, stdout))
17921792

1793+
def test_has_recursive_symlinks(self):
1794+
"""Test has_recursive_symlinks function"""
1795+
test_folder = tempfile.mkdtemp()
1796+
self.assertFalse(ft.has_recursive_symlinks(test_folder))
1797+
# Clasic Loop: Symlink to .
1798+
os.symlink('.', os.path.join(test_folder, 'self_link_dot'))
1799+
self.assertTrue(ft.has_recursive_symlinks(test_folder))
1800+
# Symlink to self
1801+
test_folder = tempfile.mkdtemp()
1802+
os.symlink('self_link', os.path.join(test_folder, 'self_link'))
1803+
self.assertTrue(ft.has_recursive_symlinks(test_folder))
1804+
# Symlink from 2 folders up
1805+
test_folder = tempfile.mkdtemp()
1806+
sub_folder = os.path.join(test_folder, 'sub1', 'sub2')
1807+
os.makedirs(sub_folder)
1808+
os.symlink(os.path.join('..', '..'), os.path.join(sub_folder, 'uplink'))
1809+
self.assertTrue(ft.has_recursive_symlinks(test_folder))
1810+
# Non-issue: Symlink to sibling folders
1811+
test_folder = tempfile.mkdtemp()
1812+
sub_folder = os.path.join(test_folder, 'sub1', 'sub2')
1813+
os.makedirs(sub_folder)
1814+
sibling_folder = os.path.join(test_folder, 'sub1', 'sibling')
1815+
os.mkdir(sibling_folder)
1816+
os.symlink('sibling', os.path.join(test_folder, 'sub1', 'sibling_link'))
1817+
os.symlink(os.path.join('..', 'sibling'), os.path.join(test_folder, sub_folder, 'sibling_link'))
1818+
self.assertFalse(ft.has_recursive_symlinks(test_folder))
1819+
# Tricky case: Sibling symlink to folder starting with the same name
1820+
os.mkdir(os.path.join(test_folder, 'sub11'))
1821+
os.symlink(os.path.join('..', 'sub11'), os.path.join(test_folder, 'sub1', 'trick_link'))
1822+
self.assertFalse(ft.has_recursive_symlinks(test_folder))
1823+
# Symlink cycle: sub1/cycle_2 -> sub2, sub2/cycle_1 -> sub1, ...
1824+
test_folder = tempfile.mkdtemp()
1825+
sub_folder1 = os.path.join(test_folder, 'sub1')
1826+
sub_folder2 = sub_folder = os.path.join(test_folder, 'sub2')
1827+
os.mkdir(sub_folder1)
1828+
os.mkdir(sub_folder2)
1829+
os.symlink(os.path.join('..', 'sub2'), os.path.join(sub_folder1, 'cycle_1'))
1830+
os.symlink(os.path.join('..', 'sub1'), os.path.join(sub_folder2, 'cycle_2'))
1831+
self.assertTrue(ft.has_recursive_symlinks(test_folder))
1832+
17931833
def test_copy_dir(self):
17941834
"""Test copy_dir function."""
17951835
testdir = os.path.dirname(os.path.abspath(__file__))

0 commit comments

Comments
 (0)