mirror of
https://github.com/python/cpython.git
synced 2025-08-04 17:08:35 +00:00
gh-115911: Ignore PermissionError during import from cwd (#116131)
Ignore PermissionError when checking cwd during import On macOS `getcwd(3)` can return EACCES if a path component isn't readable, resulting in PermissionError. `PathFinder.find_spec()` now catches these and ignores them - the same treatment as a missing/deleted cwd. Introduces `test.support.os_helper.save_mode(path, ...)`, a context manager that restores the mode of a path on exit. This is allows finer control of exception handling and robust environment restoration across platforms in `FinderTests.test_permission_error_cwd()`. Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> Co-authored-by: Brett Cannon <brett@python.org>
This commit is contained in:
parent
914c232e93
commit
a8dc6d6d44
5 changed files with 58 additions and 5 deletions
|
@ -762,10 +762,10 @@ module.
|
|||
|
||||
The current working directory -- denoted by an empty string -- is handled
|
||||
slightly differently from other entries on :data:`sys.path`. First, if the
|
||||
current working directory is found to not exist, no value is stored in
|
||||
:data:`sys.path_importer_cache`. Second, the value for the current working
|
||||
directory is looked up fresh for each module lookup. Third, the path used for
|
||||
:data:`sys.path_importer_cache` and returned by
|
||||
current working directory cannot be determined or is found not to exist, no
|
||||
value is stored in :data:`sys.path_importer_cache`. Second, the value for the
|
||||
current working directory is looked up fresh for each module lookup. Third,
|
||||
the path used for :data:`sys.path_importer_cache` and returned by
|
||||
:meth:`importlib.machinery.PathFinder.find_spec` will be the actual current
|
||||
working directory and not the empty string.
|
||||
|
||||
|
|
|
@ -1244,7 +1244,7 @@ class PathFinder:
|
|||
if path == '':
|
||||
try:
|
||||
path = _os.getcwd()
|
||||
except FileNotFoundError:
|
||||
except (FileNotFoundError, PermissionError):
|
||||
# Don't cache the failure as the cwd can easily change to
|
||||
# a valid directory later on.
|
||||
return None
|
||||
|
|
|
@ -294,6 +294,33 @@ def skip_unless_working_chmod(test):
|
|||
return test if ok else unittest.skip(msg)(test)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def save_mode(path, *, quiet=False):
|
||||
"""Context manager that restores the mode (permissions) of *path* on exit.
|
||||
|
||||
Arguments:
|
||||
|
||||
path: Path of the file to restore the mode of.
|
||||
|
||||
quiet: if False (the default), the context manager raises an exception
|
||||
on error. Otherwise, it issues only a warning and keeps the current
|
||||
working directory the same.
|
||||
|
||||
"""
|
||||
saved_mode = os.stat(path)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
try:
|
||||
os.chmod(path, saved_mode.st_mode)
|
||||
except OSError as exc:
|
||||
if not quiet:
|
||||
raise
|
||||
warnings.warn(f'tests may fail, unable to restore the mode of '
|
||||
f'{path!r} to {saved_mode.st_mode}: {exc}',
|
||||
RuntimeWarning, stacklevel=3)
|
||||
|
||||
|
||||
# Check whether the current effective user has the capability to override
|
||||
# DAC (discretionary access control). Typically user root is able to
|
||||
# bypass file read, write, and execute permission checks. The capability
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from test.support import os_helper
|
||||
from test.test_importlib import util
|
||||
|
||||
importlib = util.import_importlib('importlib')
|
||||
|
@ -153,6 +154,28 @@ class FinderTests:
|
|||
# Do not want FileNotFoundError raised.
|
||||
self.assertIsNone(self.machinery.PathFinder.find_spec('whatever'))
|
||||
|
||||
@os_helper.skip_unless_working_chmod
|
||||
def test_permission_error_cwd(self):
|
||||
# gh-115911: Test that an unreadable CWD does not break imports, in
|
||||
# particular during early stages of interpreter startup.
|
||||
with (
|
||||
os_helper.temp_dir() as new_dir,
|
||||
os_helper.save_mode(new_dir),
|
||||
os_helper.change_cwd(new_dir),
|
||||
util.import_state(path=['']),
|
||||
):
|
||||
# chmod() is done here (inside the 'with' block) because the order
|
||||
# of teardown operations cannot be the reverse of setup order. See
|
||||
# https://github.com/python/cpython/pull/116131#discussion_r1739649390
|
||||
try:
|
||||
os.chmod(new_dir, 0o000)
|
||||
except OSError:
|
||||
self.skipTest("platform does not allow "
|
||||
"changing mode of the cwd")
|
||||
|
||||
# Do not want PermissionError raised.
|
||||
self.assertIsNone(self.machinery.PathFinder.find_spec('whatever'))
|
||||
|
||||
def test_invalidate_caches_finders(self):
|
||||
# Finders with an invalidate_caches() method have it called.
|
||||
class FakeFinder:
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
If the current working directory cannot be determined due to permissions,
|
||||
then import will no longer raise :exc:`PermissionError`. Patch by Alex
|
||||
Willmer.
|
Loading…
Add table
Add a link
Reference in a new issue