Repository: spack/spack

Issue:
spack external find errors out on unreadable path
### Steps to reproduce

I ran `spack external find` and it errored out as a path is unreadable by users:

```console
$ spack external find
==> Error: [Errno 13] Permission denied: '/usr/local/lib'
$ ls -ltr /usr/local/  | grep lib
drwxr-xr-x    4 root   root  4096 Aug  5  2021 lib64/
drwx------    9 root   root  4096 Aug 27  2022 lib/
```

It looks like `spack external find` assumes `/usr/local/lib` is readable.

I naively tried `--exclude /usr/local/lib` but `--exclude` is for excluding packages.

### Error message

I also ran:
```console
spack --debug --traceback external find
```
The ending traceback is:
```
Traceback (most recent call last):
  File "/discover/nobackup/mathomp4/spack/bin/spack", line 52, in <module>
    sys.exit(main())
  File "/gpfsm/dnb44/mathomp4/spack/lib/spack/spack_installable/main.py", line 37, in main
    sys.exit(spack.main.main(argv))
  File "/gpfsm/dnb44/mathomp4/spack/lib/spack/spack/main.py", line 1024, in main
    return _main(argv)
  File "/gpfsm/dnb44/mathomp4/spack/lib/spack/spack/main.py", line 979, in _main
    return finish_parse_and_run(parser, cmd_name, env_format_error)
  File "/gpfsm/dnb44/mathomp4/spack/lib/spack/spack/main.py", line 1007, in finish_parse_and_run
    return _invoke_command(command, parser, args, unknown)
  File "/gpfsm/dnb44/mathomp4/spack/lib/spack/spack/main.py", line 648, in _invoke_command
    return_val = command(parser, args)
  File "/gpfsm/dnb44/mathomp4/spack/lib/spack/spack/cmd/external.py", line 258, in external
    action[args.external_command](args)
  File "/gpfsm/dnb44/mathomp4/spack/lib/spack/spack/cmd/external.py", line 164, in external_find
    detected_packages.update(spack.detection.by_library(pkg_cls_to_check, path_hints=args.path))
  File "/gpfsm/dnb44/mathomp4/spack/lib/spack/spack/detection/path.py", line 149, in by_library
    libraries_in_ld_and_system_library_path(path_hints=path_hints)
  File "/gpfsm/dnb44/mathomp4/spack/lib/spack/spack/detection/path.py", line 90, in libraries_in_ld_and_system_library_path
    return path_to_dict(search_paths)
  File "/gpfsm/dnb44/mathomp4/spack/lib/spack/spack/detection/common.py", line 115, in path_to_dict
    for lib in os.listdir(search_path):
PermissionError: [Errno 13] Permission denied: '/usr/local/lib'
```

I can provide the full debug if needed, but it is...long!

### Information on your system

* **Spack:** 0.21.0.dev0 (95847a0b373d1fe55772ff39d58aa616bec88932)
* **Python:** 3.9.12
* **Platform:** linux-sles12-skylake_avx512
* **Concretizer:** clingo


### General information

- [X] I have run `spack debug report` and reported the version of Spack/Python/Platform
- [X] I have searched the issues of this repo and believe this is not a duplicate
- [X] I have run the failing commands in debug mode and reported the output


Discussion:

Resolving patch:
diff --git a/lib/spack/spack/detection/common.py b/lib/spack/spack/detection/common.py
--- a/lib/spack/spack/detection/common.py
+++ b/lib/spack/spack/detection/common.py
@@ -112,10 +112,15 @@ def path_to_dict(search_paths):
     # Reverse order of search directories so that a lib in the first
     # entry overrides later entries
     for search_path in reversed(search_paths):
-        for lib in os.listdir(search_path):
-            lib_path = os.path.join(search_path, lib)
-            if llnl.util.filesystem.is_readable_file(lib_path):
-                path_to_lib[lib_path] = lib
+        try:
+            for lib in os.listdir(search_path):
+                lib_path = os.path.join(search_path, lib)
+                if llnl.util.filesystem.is_readable_file(lib_path):
+                    path_to_lib[lib_path] = lib
+        except OSError as e:
+            msg = f"cannot scan '{search_path}' for external software: {str(e)}"
+            llnl.util.tty.debug(msg)
+
     return path_to_lib
 
 


Test patch:
diff --git a/lib/spack/spack/test/cmd/external.py b/lib/spack/spack/test/cmd/external.py
--- a/lib/spack/spack/test/cmd/external.py
+++ b/lib/spack/spack/test/cmd/external.py
@@ -396,3 +396,30 @@ def test_use_tags_for_detection(command_args, mock_executable, mutable_config, m
     assert "The following specs have been" in output
     assert "cmake" in output
     assert "openssl" not in output
+
+
+@pytest.mark.regression("38733")
+@pytest.mark.skipif(sys.platform == "win32", reason="the test uses bash scripts")
+def test_failures_in_scanning_do_not_result_in_an_error(
+    mock_executable, monkeypatch, mutable_config
+):
+    """Tests that scanning paths with wrong permissions, won't cause `external find` to error."""
+    cmake_exe1 = mock_executable(
+        "cmake", output="echo cmake version 3.19.1", subdir=("first", "bin")
+    )
+    cmake_exe2 = mock_executable(
+        "cmake", output="echo cmake version 3.23.3", subdir=("second", "bin")
+    )
+
+    # Remove access from the first directory executable
+    cmake_exe1.parent.chmod(0o600)
+
+    value = os.pathsep.join([str(cmake_exe1.parent), str(cmake_exe2.parent)])
+    monkeypatch.setenv("PATH", value)
+
+    output = external("find", "cmake")
+    assert external.returncode == 0
+    assert "The following specs have been" in output
+    assert "cmake" in output
+    assert "3.23.3" in output
+    assert "3.19.1" not in output