Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit efc89e5

Browse filesBrowse files
moreatijaracobrettcannon
committed
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>
1 parent a1be83d commit efc89e5
Copy full SHA for efc89e5

File tree

5 files changed

+58
-5
lines changed
Filter options

5 files changed

+58
-5
lines changed

‎Doc/reference/import.rst

Copy file name to clipboardExpand all lines: Doc/reference/import.rst
+4-4Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -879,10 +879,10 @@ module.
879879

880880
The current working directory -- denoted by an empty string -- is handled
881881
slightly differently from other entries on :data:`sys.path`. First, if the
882-
current working directory is found to not exist, no value is stored in
883-
:data:`sys.path_importer_cache`. Second, the value for the current working
884-
directory is looked up fresh for each module lookup. Third, the path used for
885-
:data:`sys.path_importer_cache` and returned by
882+
current working directory cannot be determined or is found not to exist, no
883+
value is stored in :data:`sys.path_importer_cache`. Second, the value for the
884+
current working directory is looked up fresh for each module lookup. Third,
885+
the path used for :data:`sys.path_importer_cache` and returned by
886886
:meth:`importlib.machinery.PathFinder.find_spec` will be the actual current
887887
working directory and not the empty string.
888888

‎Lib/importlib/_bootstrap_external.py

Copy file name to clipboardExpand all lines: Lib/importlib/_bootstrap_external.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1234,7 +1234,7 @@ def _path_importer_cache(cls, path):
12341234
if path == '':
12351235
try:
12361236
path = _os.getcwd()
1237-
except FileNotFoundError:
1237+
except (FileNotFoundError, PermissionError):
12381238
# Don't cache the failure as the cwd can easily change to
12391239
# a valid directory later on.
12401240
return None

‎Lib/test/support/os_helper.py

Copy file name to clipboardExpand all lines: Lib/test/support/os_helper.py
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,33 @@ def skip_unless_working_chmod(test):
294294
return test if ok else unittest.skip(msg)(test)
295295

296296

297+
@contextlib.contextmanager
298+
def save_mode(path, *, quiet=False):
299+
"""Context manager that restores the mode (permissions) of *path* on exit.
300+
301+
Arguments:
302+
303+
path: Path of the file to restore the mode of.
304+
305+
quiet: if False (the default), the context manager raises an exception
306+
on error. Otherwise, it issues only a warning and keeps the current
307+
working directory the same.
308+
309+
"""
310+
saved_mode = os.stat(path)
311+
try:
312+
yield
313+
finally:
314+
try:
315+
os.chmod(path, saved_mode.st_mode)
316+
except OSError as exc:
317+
if not quiet:
318+
raise
319+
warnings.warn(f'tests may fail, unable to restore the mode of '
320+
f'{path!r} to {saved_mode.st_mode}: {exc}',
321+
RuntimeWarning, stacklevel=3)
322+
323+
297324
# Check whether the current effective user has the capability to override
298325
# DAC (discretionary access control). Typically user root is able to
299326
# bypass file read, write, and execute permission checks. The capability

‎Lib/test/test_importlib/import_/test_path.py

Copy file name to clipboardExpand all lines: Lib/test/test_importlib/import_/test_path.py
+23Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from test.support import os_helper
12
from test.test_importlib import util
23

34
importlib = util.import_importlib('importlib')
@@ -153,6 +154,28 @@ def test_deleted_cwd(self):
153154
# Do not want FileNotFoundError raised.
154155
self.assertIsNone(self.machinery.PathFinder.find_spec('whatever'))
155156

157+
@os_helper.skip_unless_working_chmod
158+
def test_permission_error_cwd(self):
159+
# gh-115911: Test that an unreadable CWD does not break imports, in
160+
# particular during early stages of interpreter startup.
161+
with (
162+
os_helper.temp_dir() as new_dir,
163+
os_helper.save_mode(new_dir),
164+
os_helper.change_cwd(new_dir),
165+
util.import_state(path=['']),
166+
):
167+
# chmod() is done here (inside the 'with' block) because the order
168+
# of teardown operations cannot be the reverse of setup order. See
169+
# https://github.com/python/cpython/pull/116131#discussion_r1739649390
170+
try:
171+
os.chmod(new_dir, 0o000)
172+
except OSError:
173+
self.skipTest("platform does not allow "
174+
"changing mode of the cwd")
175+
176+
# Do not want PermissionError raised.
177+
self.assertIsNone(self.machinery.PathFinder.find_spec('whatever'))
178+
156179
def test_invalidate_caches_finders(self):
157180
# Finders with an invalidate_caches() method have it called.
158181
class FakeFinder:
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
If the current working directory cannot be determined due to permissions,
2+
then import will no longer raise :exc:`PermissionError`. Patch by Alex
3+
Willmer.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.