Repository: easybuilders/easybuild-framework

Issue:
Permission problems with directories for some broken packages
Some packages have broken permissions on various files and directories.
This is only partially fixed by permissions_step.
The problem here is that it only adds/sets read permissions not executable.
I.e., a dir with
```
drwxr-x--- 2 ake folk 6 Dec 13 13:06 q/
```
does not get o+x, only o+r.

So we basically need to run adjust_permission twice, the second time with onlydirs=True and added +x bits.


Discussion:
One example is Subread which have:
```
b-an03 [ucx-master-4f90824302-pr4559]$ ls -ld /hpc2n/eb/software/MPI/GCC/7.3.0-2.30/OpenMPI/3.1.1/Subread/1.6.3/test/featureCounts
drwxr-xr-- 3 easybuild easybuild 4096 Oct  8  2018 /hpc2n/eb/software/MPI/GCC/7.3.0-2.30/OpenMPI/3.1.1/Subread/1.6.3/test/featureCounts/
```

Resolving patch:
diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py
--- a/easybuild/framework/easyblock.py
+++ b/easybuild/framework/easyblock.py
@@ -2762,19 +2762,31 @@ def permissions_step(self):
 
         # add read permissions for everybody on all files, taking into account group (if any)
         perms = stat.S_IRUSR | stat.S_IRGRP
+        # directory permissions: readable (r) & searchable (x)
+        dir_perms = stat.S_IXUSR | stat.S_IXGRP
         self.log.debug("Ensuring read permissions for user/group on install dir (recursively)")
+
         if self.group is None:
             perms |= stat.S_IROTH
+            dir_perms |= stat.S_IXOTH
             self.log.debug("Also ensuring read permissions for others on install dir (no group specified)")
 
         umask = build_option('umask')
         if umask is not None:
             # umask is specified as a string, so interpret it first as integer in octal, then take complement (~)
             perms &= ~int(umask, 8)
+            dir_perms &= ~int(umask, 8)
             self.log.debug("Taking umask '%s' into account when ensuring read permissions to install dir", umask)
 
+        self.log.debug("Adding file read permissions in %s using '%s'", self.installdir, oct(perms))
         adjust_permissions(self.installdir, perms, add=True, recursive=True, relative=True, ignore_errors=True)
-        self.log.info("Successfully added read permissions '%s' recursively on install dir", oct(perms))
+
+        # also ensure directories have exec permissions (so they can be opened)
+        self.log.debug("Adding directory search permissions in %s using '%s'", self.installdir, oct(dir_perms))
+        adjust_permissions(self.installdir, dir_perms, add=True, recursive=True, relative=True, onlydirs=True,
+                           ignore_errors=True)
+
+        self.log.info("Successfully added read permissions recursively on install dir %s", self.installdir)
 
     def test_cases_step(self):
         """


Test patch:
diff --git a/test/framework/toy_build.py b/test/framework/toy_build.py
--- a/test/framework/toy_build.py
+++ b/test/framework/toy_build.py
@@ -443,6 +443,14 @@ def test_toy_download_sources(self):
     def test_toy_permissions(self):
         """Test toy build with custom umask settings."""
         toy_ec_file = os.path.join(os.path.dirname(__file__), 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0.eb')
+        test_ec_txt = read_file(toy_ec_file)
+
+        # remove exec perms on bin subdirectory for others, to check whether correct dir permissions are set
+        test_ec_txt += "\npostinstallcmds += ['chmod o-x %(installdir)s/bin']"
+
+        test_ec = os.path.join(self.test_prefix, 'test.eb')
+        write_file(test_ec, test_ec_txt)
+
         args = [
             '--sourcepath=%s' % self.test_sourcepath,
             '--buildpath=%s' % self.test_buildpath,
@@ -456,7 +464,7 @@ def test_toy_permissions(self):
         orig_umask = os.umask(0o022)
 
         # test specifying a non-existing group
-        allargs = [toy_ec_file] + args + ['--group=thisgroupdoesnotexist']
+        allargs = [test_ec] + args + ['--group=thisgroupdoesnotexist']
         outtxt, err = self.eb_main(allargs, logfile=self.dummylogfn, do_build=True, return_error=True)
         err_regex = re.compile("Failed to get group ID .* group does not exist")
         self.assertTrue(err_regex.search(outtxt), "Pattern '%s' found in '%s'" % (err_regex.pattern, outtxt))
@@ -479,10 +487,10 @@ def test_toy_permissions(self):
             shutil.rmtree(self.test_installpath)
 
             if cfg_group is None and ec_group is None:
-                allargs = [toy_ec_file]
+                allargs = [test_ec]
             elif ec_group is not None:
-                shutil.copy2(toy_ec_file, self.test_buildpath)
-                tmp_ec_file = os.path.join(self.test_buildpath, os.path.basename(toy_ec_file))
+                shutil.copy2(test_ec, self.test_buildpath)
+                tmp_ec_file = os.path.join(self.test_buildpath, os.path.basename(test_ec))
                 write_file(tmp_ec_file, "\ngroup = '%s'" % ec_group, append=True)
                 allargs = [tmp_ec_file]
             allargs.extend(args)
@@ -527,7 +535,7 @@ def test_toy_permissions(self):
                 perms = os.stat(fullpath).st_mode & 0o777
                 tup = (fullpath, oct(correct_perms), oct(perms), umask, cfg_group, ec_group)
                 msg = "Path %s has %s permissions: %s (umask: %s, group: %s - %s)" % tup
-                self.assertEqual(perms, correct_perms, msg)
+                self.assertEqual(oct(perms), oct(correct_perms), msg)
                 if group is not None:
                     path_gid = os.stat(fullpath).st_gid
                     self.assertEqual(path_gid, grp.getgrnam(group).gr_gid)