Repository: spack/spack

Issue:
Spack BootStrap Status fails due to path
### Steps to reproduce

On Ubuntu 22.04, I am able to use Spack with no issues to install various packages. However, when I try to confine the Spack application inside a snap running Ubuntu Core 22 I am unable to to run the `spack bootstrap status` command.

```console
# Clone Spack Snap Repo
git clone https://github.com/dvdgomez/spack-snap.git

# Build Snap
snapcraft

# Install locally built snap
sudo snap install spack_0.20.0_amd64.snap --dangerous

# Run command
spack bootstrap status

```

### Error message

My current workaround patch is to change the failing search path from `'/snap/spack/x1/usr/sbin', '/snap/spack/x1/usr/bin', '/snap/spack/x1/bin', '/usr/sbin', '/usr/bin', '/sbin', '/bin', '/usr/games'` -> `'/snap/spack/x1/usr/sbin', '/snap/spack/x1/usr/bin', '/snap/spack/x1/bin'` and that works fine. Maybe there is a way for it to silently ignore the rest of the path. 

```console
$ spack --debug --stacktrace bootstrap status
lib/spack/spack/cmd/__init__.py:123 ==> [2023-06-07-10:53:38.874656] Imported bootstrap from built-in commands
lib/spack/spack/cmd/__init__.py:123 ==> [2023-06-07-10:53:38.876897] Imported bootstrap from built-in commands
Spack v0.20.0 - python@3.10

lib/spack/spack/config.py:996 ==> [2023-06-07-10:53:38.877797] Reading config from file /snap/spack/x1/spack/etc/spack/defaults/bootstrap.yaml
lib/spack/spack/config.py:996 ==> [2023-06-07-10:53:38.883935] Reading config from file /snap/spack/x1/spack/etc/spack/defaults/config.yaml
lib/spack/spack/config.py:996 ==> [2023-06-07-10:53:38.898723] Reading config from file /home/dave/snap/spack/common/config.yaml
lib/spack/spack/config.py:996 ==> [2023-06-07-10:53:38.916174] Reading config from file /snap/spack/x1/spack/etc/spack/defaults/repos.yaml
lib/spack/spack/config.py:996 ==> [2023-06-07-10:53:38.919483] Reading config from file /snap/spack/x1/spack/etc/spack/defaults/config.yaml
lib/spack/spack/config.py:996 ==> [2023-06-07-10:53:38.943055] Reading config from file /home/dave/snap/spack/common/config.yaml
lib/spack/spack/database.py:391 ==> [2023-06-07-10:53:38.955921] DATABASE LOCK TIMEOUT: 60s
lib/spack/spack/database.py:397 ==> [2023-06-07-10:53:38.956057] PACKAGE LOCK TIMEOUT: No timeout
lib/spack/spack/database.py:391 ==> [2023-06-07-10:53:38.956575] DATABASE LOCK TIMEOUT: 60s
lib/spack/spack/database.py:397 ==> [2023-06-07-10:53:38.956698] PACKAGE LOCK TIMEOUT: No timeout
lib/spack/spack/bootstrap/config.py:125 ==> [2023-06-07-10:53:38.956816] [BOOTSTRAP CONFIG SCOPE] name=_builtin
lib/spack/spack/config.py:996 ==> [2023-06-07-10:53:38.957142] Reading config from file /snap/spack/x1/spack/etc/spack/defaults/bootstrap.yaml
lib/spack/spack/bootstrap/config.py:138 ==> [2023-06-07-10:53:38.961860] [BOOTSTRAP CONFIG SCOPE] name=defaults, path=/snap/spack/x1/spack/etc/spack/defaults
lib/spack/spack/bootstrap/config.py:139 ==> [2023-06-07-10:53:38.961973] [BOOTSTRAP CONFIG SCOPE] name=defaults/linux, path=/snap/spack/x1/spack/etc/spack/defaults/linux
lib/spack/spack/bootstrap/config.py:138 ==> [2023-06-07-10:53:38.962077] [BOOTSTRAP CONFIG SCOPE] name=bootstrap, path=/home/dave/snap/spack/x1/.spack/bootstrap/config
lib/spack/spack/bootstrap/config.py:139 ==> [2023-06-07-10:53:38.962171] [BOOTSTRAP CONFIG SCOPE] name=bootstrap/linux, path=/home/dave/snap/spack/x1/.spack/bootstrap/config/linux
lib/spack/spack/config.py:996 ==> [2023-06-07-10:53:38.966181] Reading config from file /snap/spack/x1/spack/etc/spack/defaults/config.yaml
lib/spack/spack/config.py:996 ==> [2023-06-07-10:53:38.979742] Reading config from file /home/dave/snap/spack/common/config.yaml
Traceback (most recent call last):
  File "/snap/spack/x1/spack/lib/spack/llnl/util/lang.py", line 193, in _memoized_function
    return func.cache[key]
KeyError: ('/snap/spack/x1/usr/sbin', '/snap/spack/x1/usr/bin', '/snap/spack/x1/bin', '/usr/sbin', '/usr/bin', '/sbin', '/bin', '/usr/games')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/snap/spack/x1/spack/bin/spack", line 54, in <module>
    sys.exit(main())
  File "/snap/spack/x1/spack/lib/spack/spack_installable/main.py", line 37, in main
    sys.exit(spack.main.main(argv))
  File "/snap/spack/x1/spack/lib/spack/spack/main.py", line 1018, in main
    return _main(argv)
  File "/snap/spack/x1/spack/lib/spack/spack/main.py", line 973, in _main
    return finish_parse_and_run(parser, cmd_name, env_format_error)
  File "/snap/spack/x1/spack/lib/spack/spack/main.py", line 1001, in finish_parse_and_run
    return _invoke_command(command, parser, args, unknown)
  File "/snap/spack/x1/spack/lib/spack/spack/main.py", line 650, in _invoke_command
    return_val = command(parser, args)
  File "/snap/spack/x1/spack/lib/spack/spack/cmd/bootstrap.py", line 468, in bootstrap
    callbacks[args.subcommand](args)
  File "/snap/spack/x1/spack/lib/spack/spack/cmd/bootstrap.py", line 316, in _status
    with spack.bootstrap.ensure_bootstrap_configuration():
  File "/usr/lib/python3.10/contextlib.py", line 135, in __enter__
    return next(self.gen)
  File "/snap/spack/x1/spack/lib/spack/spack/bootstrap/config.py", line 105, in ensure_bootstrap_configuration
    with _ensure_bootstrap_configuration():
  File "/usr/lib/python3.10/contextlib.py", line 135, in __enter__
    return next(self.gen)
  File "/snap/spack/x1/spack/lib/spack/spack/bootstrap/config.py", line 167, in _ensure_bootstrap_configuration
    _add_compilers_if_missing()
  File "/snap/spack/x1/spack/lib/spack/spack/bootstrap/config.py", line 145, in _add_compilers_if_missing
    if not spack.compilers.compilers_for_arch(arch):
  File "/snap/spack/x1/spack/lib/spack/spack/compilers/__init__.py", line 362, in compilers_for_arch
    config = all_compilers_config(scope)
  File "/snap/spack/x1/spack/lib/spack/spack/compilers/__init__.py", line 210, in all_compilers_config
    _cache_config_file = get_compiler_config(scope, init_config)
  File "/snap/spack/x1/spack/lib/spack/spack/compilers/__init__.py", line 130, in get_compiler_config
    init_compiler_config()
  File "/snap/spack/x1/spack/lib/spack/spack/compilers/__init__.py", line 117, in init_compiler_config
    compilers = find_compilers()
  File "/snap/spack/x1/spack/lib/spack/spack/compilers/__init__.py", line 242, in find_compilers
    arguments.extend(arguments_to_detect_version_fn(o, search_paths))
  File "/snap/spack/x1/spack/lib/spack/spack/compilers/__init__.py", line 651, in arguments_to_detect_version_fn
    return fn(paths)
  File "/snap/spack/x1/spack/lib/spack/spack/compilers/__init__.py", line 628, in _default
    files_to_be_tested = fs.files_in(*search_paths)
  File "/snap/spack/x1/spack/lib/spack/spack/util/path.py", line 198, in path_filter_caller
    return func(*args, **kwargs)
  File "/snap/spack/x1/spack/lib/spack/llnl/util/lang.py", line 195, in _memoized_function
    ret = func(*args, **kwargs)
  File "/snap/spack/x1/spack/lib/spack/llnl/util/filesystem.py", line 2520, in files_in
    lambda x: os.path.isfile(x[1]), [(f, os.path.join(d, f)) for f in os.listdir(d)]
PermissionError: [Errno 13] Permission denied: '/usr/sbin'
```

### Information on your system

```console
$ spack debug report
* **Spack:** 0.20.0
* **Python:** 3.10.6
* **Platform:** linux-ubuntu_core22-zen3
* **Concretizer:** clingo
```
**compilers.yaml**

```console
compilers:
    - compiler:
        spec: gcc@11.3.0
        paths:
          cc: /snap/spack/x1/usr/bin/gcc-11
          cxx: /snap/spack/x1/usr/bin/g++-11
          f77: /snap/spack/x1/usr/bin/gfortran
          fc: /snap/spack/x1/usr/bin/gfortran
        flags: {}
        operating_system: ubuntu_core22
        target: x86_64
        modules: []
        environment:
          prepend_path:
            LD_LIBRARY_PATH: /snap/spack/x1/usr/lib/x86_64-linux-gnu
        extra_rpaths: []
```

@massi

### 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/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py
--- a/lib/spack/llnl/util/filesystem.py
+++ b/lib/spack/llnl/util/filesystem.py
@@ -610,6 +610,8 @@ def chgrp(path, group, follow_symlinks=True):
         gid = grp.getgrnam(group).gr_gid
     else:
         gid = group
+    if os.stat(path).st_gid == gid:
+        return
     if follow_symlinks:
         os.chown(path, -1, gid)
     else:


Test patch:
diff --git a/lib/spack/spack/test/llnl/util/filesystem.py b/lib/spack/spack/test/llnl/util/filesystem.py
--- a/lib/spack/spack/test/llnl/util/filesystem.py
+++ b/lib/spack/spack/test/llnl/util/filesystem.py
@@ -502,6 +502,40 @@ def test_filter_files_with_different_encodings(regex, replacement, filename, tmp
         assert replacement in f.read()
 
 
+@pytest.mark.skipif(sys.platform == "win32", reason="chgrp isn't used on Windows")
+def test_chgrp_dont_set_group_if_already_set(tmpdir, monkeypatch):
+    with fs.working_dir(tmpdir):
+        os.mkdir("test-dir_chgrp_dont_set_group_if_already_set")
+
+    def _fail(*args, **kwargs):
+        raise Exception("chrgrp should not be called")
+
+    class FakeStat(object):
+        def __init__(self, gid):
+            self.st_gid = gid
+
+    original_stat = os.stat
+
+    def _stat(*args, **kwargs):
+        path = args[0]
+        if path == "test-dir_chgrp_dont_set_group_if_already_set":
+            return FakeStat(gid=1001)
+        else:
+            # Monkeypatching stat can interfere with post-test cleanup, so for
+            # paths that aren't part of the test, we want the original behavior
+            # of stat
+            return original_stat(*args, **kwargs)
+
+    monkeypatch.setattr(os, "chown", _fail)
+    monkeypatch.setattr(os, "lchown", _fail)
+    monkeypatch.setattr(os, "stat", _stat)
+
+    with fs.working_dir(tmpdir):
+        with pytest.raises(Exception):
+            fs.chgrp("test-dir_chgrp_dont_set_group_if_already_set", 1002)
+        fs.chgrp("test-dir_chgrp_dont_set_group_if_already_set", 1001)
+
+
 def test_filter_files_multiple(tmpdir):
     # All files given as input to this test must satisfy the pre-requisite
     # that the 'replacement' string is not present in the file initially and