Skip to content

Commit

Permalink
Skip $PATH entries we cannot check rather than dying with PermissionE…
Browse files Browse the repository at this point in the history
…rror

On Fedora CI, I used runuser to get rid of root privileges before testing virtualenv.

The invocation I used did not clear the root's $PATH and as a result,
one of the entries in $PATH was /root/.local/bin.
But /root/ does not allow other users to browse (execute, read).

That lead to an unhanded PermissionError.

When this happens, the $PATH entry is now skipped as if it did not exist.

As there might be other reasons we cannot check if the $PATH entry exists,
I decided to suppress all OSError types.

For clarity, here is the Traceback I was getting:

    # runuser testuser -c 'virtualenv --with-traceback --python pypy3.10 venv'
    Traceback (most recent call last):
      File "/usr/bin/virtualenv", line 8, in <module>
        sys.exit(run_with_catch())
                 ~~~~~~~~~~~~~~^^
      File "/usr/lib/python3.13/site-packages/virtualenv/__main__.py", line 56, in run_with_catch
        run(args, options, env)
        ~~~^^^^^^^^^^^^^^^^^^^^
      File "/usr/lib/python3.13/site-packages/virtualenv/__main__.py", line 18, in run
        session = cli_run(args, options, env)
      File "/usr/lib/python3.13/site-packages/virtualenv/run/__init__.py", line 31, in cli_run
        of_session = session_via_cli(args, options, setup_logging, env)
      File "/usr/lib/python3.13/site-packages/virtualenv/run/__init__.py", line 49, in session_via_cli
        parser, elements = build_parser(args, options, setup_logging, env)
                           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/usr/lib/python3.13/site-packages/virtualenv/run/__init__.py", line 77, in build_parser
        parser._interpreter = interpreter = discover.interpreter  # noqa: SLF001
                                            ^^^^^^^^^^^^^^^^^^^^
      File "/usr/lib/python3.13/site-packages/virtualenv/discovery/discover.py", line 41, in interpreter
        self._interpreter = self.run()
                            ~~~~~~~~^^
      File "/usr/lib/python3.13/site-packages/virtualenv/discovery/builtin.py", line 58, in run
        result = get_interpreter(python_spec, self.try_first_with, self.app_data, self._env)
      File "/usr/lib/python3.13/site-packages/virtualenv/discovery/builtin.py", line 75, in get_interpreter
        for interpreter, impl_must_match in propose_interpreters(spec, try_first_with, app_data, env):
                                            ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/usr/lib/python3.13/site-packages/virtualenv/discovery/builtin.py", line 147, in propose_interpreters
        for pos, path in enumerate(get_paths(env)):
                         ~~~~~~~~~^^^^^^^^^^^^^^^^
      File "/usr/lib/python3.13/site-packages/virtualenv/discovery/builtin.py", line 169, in get_paths
        if p.exists():
           ~~~~~~~~^^
      File "/usr/lib64/python3.13/pathlib/_abc.py", line 450, in exists
        self.stat(follow_symlinks=follow_symlinks)
        ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/usr/lib64/python3.13/pathlib/_local.py", line 515, in stat
        return os.stat(self, follow_symlinks=follow_symlinks)
               ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    PermissionError: [Errno 13] Permission denied: '/root/.local/bin'
  • Loading branch information
hroncok committed Oct 16, 2024
1 parent 228b615 commit 4b6acb3
Show file tree
Hide file tree
Showing 3 changed files with 13 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/changelog/2782.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
When a ``$PATH`` entry cannot be checked for existence, skip it instead of terminating - by :user:`hroncok`.
6 changes: 4 additions & 2 deletions src/virtualenv/discovery/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
import os
import sys
from contextlib import suppress
from pathlib import Path
from typing import TYPE_CHECKING, Callable

Expand Down Expand Up @@ -166,8 +167,9 @@ def get_paths(env: Mapping[str, str]) -> Generator[Path, None, None]:
path = os.defpath
if path:
for p in map(Path, path.split(os.pathsep)):
if p.exists():
yield p
with suppress(OSError):
if p.exists():
yield p


class LazyPathDump:
Expand Down
8 changes: 8 additions & 0 deletions tests/unit/discovery/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ def test_discovery_via_path_not_found(tmp_path, monkeypatch):
assert interpreter is None


def test_discovery_via_path_in_nonbrowseable_directory(tmp_path, monkeypatch):
bad_perm = tmp_path / "bad_perm"
bad_perm.mkdir(mode=0o000)
monkeypatch.setenv("PATH", str(bad_perm / "bin"))
interpreter = get_interpreter(uuid4().hex, [])
assert interpreter is None


def test_relative_path(session_app_data, monkeypatch):
sys_executable = Path(PythonInfo.current_system(app_data=session_app_data).system_executable)
cwd = sys_executable.parents[1]
Expand Down

0 comments on commit 4b6acb3

Please sign in to comment.