Skip to content

Commit 9082054

Browse files
authored
Merge pull request #3551 from Flamefire/create_unused_dir
add create_unused_dir function to create a directory which does not yet exist
2 parents aea60d7 + 14b99ef commit 9082054

File tree

2 files changed

+155
-12
lines changed

2 files changed

+155
-12
lines changed

easybuild/tools/filetools.py

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,6 +1667,25 @@ def patch_perl_script_autoflush(path):
16671667
write_file(path, newtxt)
16681668

16691669

1670+
def set_gid_sticky_bits(path, set_gid=None, sticky=None, recursive=False):
1671+
"""Set GID/sticky bits on specified path."""
1672+
if set_gid is None:
1673+
set_gid = build_option('set_gid_bit')
1674+
if sticky is None:
1675+
sticky = build_option('sticky_bit')
1676+
1677+
bits = 0
1678+
if set_gid:
1679+
bits |= stat.S_ISGID
1680+
if sticky:
1681+
bits |= stat.S_ISVTX
1682+
if bits:
1683+
try:
1684+
adjust_permissions(path, bits, add=True, relative=True, recursive=recursive, onlydirs=True)
1685+
except OSError as err:
1686+
raise EasyBuildError("Failed to set groud ID/sticky bit: %s", err)
1687+
1688+
16701689
def mkdir(path, parents=False, set_gid=None, sticky=None):
16711690
"""
16721691
Create a directory
@@ -1702,18 +1721,9 @@ def mkdir(path, parents=False, set_gid=None, sticky=None):
17021721
raise EasyBuildError("Failed to create directory %s: %s", path, err)
17031722

17041723
# set group ID and sticky bits, if desired
1705-
bits = 0
1706-
if set_gid:
1707-
bits |= stat.S_ISGID
1708-
if sticky:
1709-
bits |= stat.S_ISVTX
1710-
if bits:
1711-
try:
1712-
new_subdir = path[len(existing_parent_path):].lstrip(os.path.sep)
1713-
new_path = os.path.join(existing_parent_path, new_subdir.split(os.path.sep)[0])
1714-
adjust_permissions(new_path, bits, add=True, relative=True, recursive=True, onlydirs=True)
1715-
except OSError as err:
1716-
raise EasyBuildError("Failed to set groud ID/sticky bit: %s", err)
1724+
new_subdir = path[len(existing_parent_path):].lstrip(os.path.sep)
1725+
new_path = os.path.join(existing_parent_path, new_subdir.split(os.path.sep)[0])
1726+
set_gid_sticky_bits(new_path, set_gid, sticky, recursive=True)
17171727
else:
17181728
_log.debug("Not creating existing path %s" % path)
17191729

@@ -2588,3 +2598,32 @@ def copy_framework_files(paths, target_dir):
25882598
raise EasyBuildError("Couldn't find parent folder of updated file: %s", path)
25892599

25902600
return file_info
2601+
2602+
2603+
def create_unused_dir(parent_folder, name):
2604+
"""
2605+
Create a new folder in parent_folder using name as the name.
2606+
When a folder of that name already exists, '_0' is appended which is retried for increasing numbers until
2607+
an unused name was found
2608+
"""
2609+
if not os.path.isabs(parent_folder):
2610+
parent_folder = os.path.abspath(parent_folder)
2611+
2612+
start_path = os.path.join(parent_folder, name)
2613+
for number in range(-1, 10000): # Start with no suffix and limit the number of attempts
2614+
if number < 0:
2615+
path = start_path
2616+
else:
2617+
path = start_path + '_' + str(number)
2618+
try:
2619+
os.mkdir(path)
2620+
break
2621+
except OSError as err:
2622+
# Distinguish between error due to existing folder and anything else
2623+
if not os.path.exists(path):
2624+
raise EasyBuildError("Failed to create directory %s: %s", path, err)
2625+
2626+
# set group ID and sticky bits, if desired
2627+
set_gid_sticky_bits(path, recursive=True)
2628+
2629+
return path

test/framework/filetools.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2947,6 +2947,110 @@ def test_locate_files(self):
29472947
error_pattern = r"One or more files not found: 2\.txt \(search paths: \)"
29482948
self.assertErrorRegex(EasyBuildError, error_pattern, ft.locate_files, ['2.txt'], [])
29492949

2950+
def test_set_gid_sticky_bits(self):
2951+
"""Test for set_gid_sticky_bits function."""
2952+
test_dir = os.path.join(self.test_prefix, 'test_dir')
2953+
test_subdir = os.path.join(test_dir, 'subdir')
2954+
2955+
ft.mkdir(test_subdir, parents=True)
2956+
dir_perms = os.lstat(test_dir)[stat.ST_MODE]
2957+
self.assertEqual(dir_perms & stat.S_ISGID, 0)
2958+
self.assertEqual(dir_perms & stat.S_ISVTX, 0)
2959+
dir_perms = os.lstat(test_subdir)[stat.ST_MODE]
2960+
self.assertEqual(dir_perms & stat.S_ISGID, 0)
2961+
self.assertEqual(dir_perms & stat.S_ISVTX, 0)
2962+
2963+
# by default, GID & sticky bits are not set
2964+
ft.set_gid_sticky_bits(test_dir)
2965+
dir_perms = os.lstat(test_dir)[stat.ST_MODE]
2966+
self.assertEqual(dir_perms & stat.S_ISGID, 0)
2967+
self.assertEqual(dir_perms & stat.S_ISVTX, 0)
2968+
2969+
ft.set_gid_sticky_bits(test_dir, set_gid=True)
2970+
dir_perms = os.lstat(test_dir)[stat.ST_MODE]
2971+
self.assertEqual(dir_perms & stat.S_ISGID, stat.S_ISGID)
2972+
self.assertEqual(dir_perms & stat.S_ISVTX, 0)
2973+
ft.remove_dir(test_dir)
2974+
ft.mkdir(test_subdir, parents=True)
2975+
2976+
ft.set_gid_sticky_bits(test_dir, sticky=True)
2977+
dir_perms = os.lstat(test_dir)[stat.ST_MODE]
2978+
self.assertEqual(dir_perms & stat.S_ISGID, 0)
2979+
self.assertEqual(dir_perms & stat.S_ISVTX, stat.S_ISVTX)
2980+
ft.remove_dir(test_dir)
2981+
ft.mkdir(test_subdir, parents=True)
2982+
2983+
ft.set_gid_sticky_bits(test_dir, set_gid=True, sticky=True)
2984+
dir_perms = os.lstat(test_dir)[stat.ST_MODE]
2985+
self.assertEqual(dir_perms & stat.S_ISGID, stat.S_ISGID)
2986+
self.assertEqual(dir_perms & stat.S_ISVTX, stat.S_ISVTX)
2987+
# no recursion by default
2988+
dir_perms = os.lstat(test_subdir)[stat.ST_MODE]
2989+
self.assertEqual(dir_perms & stat.S_ISGID, 0)
2990+
self.assertEqual(dir_perms & stat.S_ISVTX, 0)
2991+
2992+
ft.remove_dir(test_dir)
2993+
ft.mkdir(test_subdir, parents=True)
2994+
2995+
ft.set_gid_sticky_bits(test_dir, set_gid=True, sticky=True, recursive=True)
2996+
dir_perms = os.lstat(test_dir)[stat.ST_MODE]
2997+
self.assertEqual(dir_perms & stat.S_ISGID, stat.S_ISGID)
2998+
self.assertEqual(dir_perms & stat.S_ISVTX, stat.S_ISVTX)
2999+
dir_perms = os.lstat(test_subdir)[stat.ST_MODE]
3000+
self.assertEqual(dir_perms & stat.S_ISGID, stat.S_ISGID)
3001+
self.assertEqual(dir_perms & stat.S_ISVTX, stat.S_ISVTX)
3002+
3003+
ft.remove_dir(test_dir)
3004+
ft.mkdir(test_subdir, parents=True)
3005+
3006+
# set_gid_sticky_bits honors relevant build options
3007+
init_config(build_options={'set_gid_bit': True, 'sticky_bit': True})
3008+
ft.set_gid_sticky_bits(test_dir, recursive=True)
3009+
dir_perms = os.lstat(test_dir)[stat.ST_MODE]
3010+
self.assertEqual(dir_perms & stat.S_ISGID, stat.S_ISGID)
3011+
self.assertEqual(dir_perms & stat.S_ISVTX, stat.S_ISVTX)
3012+
dir_perms = os.lstat(test_subdir)[stat.ST_MODE]
3013+
self.assertEqual(dir_perms & stat.S_ISGID, stat.S_ISGID)
3014+
self.assertEqual(dir_perms & stat.S_ISVTX, stat.S_ISVTX)
3015+
3016+
def test_create_unused_dir(self):
3017+
"""Test create_unused_dir function."""
3018+
path = ft.create_unused_dir(self.test_prefix, 'folder')
3019+
self.assertEqual(path, os.path.join(self.test_prefix, 'folder'))
3020+
self.assertTrue(os.path.exists(path))
3021+
3022+
# Repeat with existing folder(s) should create new ones
3023+
for i in range(10):
3024+
path = ft.create_unused_dir(self.test_prefix, 'folder')
3025+
self.assertEqual(path, os.path.join(self.test_prefix, 'folder_%s' % i))
3026+
self.assertTrue(os.path.exists(path))
3027+
3028+
# Not influenced by similar folder
3029+
path = ft.create_unused_dir(self.test_prefix, 'folder2')
3030+
self.assertEqual(path, os.path.join(self.test_prefix, 'folder2'))
3031+
self.assertTrue(os.path.exists(path))
3032+
for i in range(10):
3033+
path = ft.create_unused_dir(self.test_prefix, 'folder2')
3034+
self.assertEqual(path, os.path.join(self.test_prefix, 'folder2_%s' % i))
3035+
self.assertTrue(os.path.exists(path))
3036+
3037+
# Fail cleanly if passed a readonly folder
3038+
readonly_dir = os.path.join(self.test_prefix, 'ro_folder')
3039+
ft.mkdir(readonly_dir)
3040+
old_perms = os.lstat(readonly_dir)[stat.ST_MODE]
3041+
ft.adjust_permissions(readonly_dir, stat.S_IREAD | stat.S_IEXEC, relative=False)
3042+
try:
3043+
self.assertErrorRegex(EasyBuildError, 'Failed to create directory',
3044+
ft.create_unused_dir, readonly_dir, 'new_folder')
3045+
finally:
3046+
ft.adjust_permissions(readonly_dir, old_perms, relative=False)
3047+
3048+
# Ignore files same as folders. So first just create a file with no contents
3049+
ft.write_file(os.path.join(self.test_prefix, 'file'), '')
3050+
path = ft.create_unused_dir(self.test_prefix, 'file')
3051+
self.assertEqual(path, os.path.join(self.test_prefix, 'file_0'))
3052+
self.assertTrue(os.path.exists(path))
3053+
29503054

29513055
def suite():
29523056
""" returns all the testcases in this module """

0 commit comments

Comments
 (0)