mirror of
https://github.com/python/cpython.git
synced 2025-09-27 18:59:43 +00:00
GH-90208: Suppress OSError exceptions from pathlib.Path.glob()
(GH-104141)
`pathlib.Path.glob()` now suppresses all OSError exceptions, except those raised from calling `is_dir()` on the top-level path. Previously, `glob()` suppressed ENOENT, ENOTDIR, EBADF and ELOOP errors and their Windows equivalents. PermissionError was also suppressed unless it occurred when calling `is_dir()` on the top-level path. However, the selector would abort prematurely if a PermissionError was raised, and so `glob()` could return incomplete results.
This commit is contained in:
parent
373bca0cc5
commit
94f30c7557
3 changed files with 28 additions and 45 deletions
|
@ -142,25 +142,21 @@ class _WildcardSelector(_Selector):
|
||||||
# avoid exhausting file descriptors when globbing deep trees.
|
# avoid exhausting file descriptors when globbing deep trees.
|
||||||
with scandir(parent_path) as scandir_it:
|
with scandir(parent_path) as scandir_it:
|
||||||
entries = list(scandir_it)
|
entries = list(scandir_it)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
if self.dironly:
|
if self.dironly:
|
||||||
try:
|
try:
|
||||||
# "entry.is_dir()" can raise PermissionError
|
|
||||||
# in some cases (see bpo-38894), which is not
|
|
||||||
# among the errors ignored by _ignore_error()
|
|
||||||
if not entry.is_dir():
|
if not entry.is_dir():
|
||||||
continue
|
continue
|
||||||
except OSError as e:
|
except OSError:
|
||||||
if not _ignore_error(e):
|
|
||||||
raise
|
|
||||||
continue
|
continue
|
||||||
name = entry.name
|
name = entry.name
|
||||||
if self.match(name):
|
if self.match(name):
|
||||||
path = parent_path._make_child_relpath(name)
|
path = parent_path._make_child_relpath(name)
|
||||||
for p in self.successor._select_from(path, scandir):
|
for p in self.successor._select_from(path, scandir):
|
||||||
yield p
|
yield p
|
||||||
except PermissionError:
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
class _RecursiveWildcardSelector(_Selector):
|
class _RecursiveWildcardSelector(_Selector):
|
||||||
|
@ -175,28 +171,25 @@ class _RecursiveWildcardSelector(_Selector):
|
||||||
# avoid exhausting file descriptors when globbing deep trees.
|
# avoid exhausting file descriptors when globbing deep trees.
|
||||||
with scandir(parent_path) as scandir_it:
|
with scandir(parent_path) as scandir_it:
|
||||||
entries = list(scandir_it)
|
entries = list(scandir_it)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
entry_is_dir = False
|
entry_is_dir = False
|
||||||
try:
|
try:
|
||||||
entry_is_dir = entry.is_dir(follow_symlinks=False)
|
entry_is_dir = entry.is_dir(follow_symlinks=False)
|
||||||
except OSError as e:
|
except OSError:
|
||||||
if not _ignore_error(e):
|
pass
|
||||||
raise
|
|
||||||
if entry_is_dir:
|
if entry_is_dir:
|
||||||
path = parent_path._make_child_relpath(entry.name)
|
path = parent_path._make_child_relpath(entry.name)
|
||||||
for p in self._iterate_directories(path, scandir):
|
for p in self._iterate_directories(path, scandir):
|
||||||
yield p
|
yield p
|
||||||
except PermissionError:
|
|
||||||
return
|
|
||||||
|
|
||||||
def _select_from(self, parent_path, scandir):
|
def _select_from(self, parent_path, scandir):
|
||||||
try:
|
successor_select = self.successor._select_from
|
||||||
successor_select = self.successor._select_from
|
for starting_point in self._iterate_directories(parent_path, scandir):
|
||||||
for starting_point in self._iterate_directories(parent_path, scandir):
|
for p in successor_select(starting_point, scandir):
|
||||||
for p in successor_select(starting_point, scandir):
|
yield p
|
||||||
yield p
|
|
||||||
except PermissionError:
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
class _DoubleRecursiveWildcardSelector(_RecursiveWildcardSelector):
|
class _DoubleRecursiveWildcardSelector(_RecursiveWildcardSelector):
|
||||||
|
|
|
@ -1949,33 +1949,19 @@ class _BasePathTest(object):
|
||||||
P = self.cls
|
P = self.cls
|
||||||
base = P(BASE) / 'permissions'
|
base = P(BASE) / 'permissions'
|
||||||
base.mkdir()
|
base.mkdir()
|
||||||
|
self.addCleanup(os_helper.rmtree, base)
|
||||||
|
|
||||||
file1 = base / "file1"
|
for i in range(100):
|
||||||
file1.touch()
|
link = base / f"link{i}"
|
||||||
file2 = base / "file2"
|
if i % 2:
|
||||||
file2.touch()
|
link.symlink_to(P(BASE, "dirE", "nonexistent"))
|
||||||
|
else:
|
||||||
|
link.symlink_to(P(BASE, "dirC"))
|
||||||
|
|
||||||
subdir = base / "subdir"
|
self.assertEqual(len(set(base.glob("*"))), 100)
|
||||||
|
self.assertEqual(len(set(base.glob("*/"))), 50)
|
||||||
file3 = base / "file3"
|
self.assertEqual(len(set(base.glob("*/fileC"))), 50)
|
||||||
file3.symlink_to(subdir / "other")
|
self.assertEqual(len(set(base.glob("*/file*"))), 50)
|
||||||
|
|
||||||
# Patching is needed to avoid relying on the filesystem
|
|
||||||
# to return the order of the files as the error will not
|
|
||||||
# happen if the symlink is the last item.
|
|
||||||
real_scandir = os.scandir
|
|
||||||
def my_scandir(path):
|
|
||||||
with real_scandir(path) as scandir_it:
|
|
||||||
entries = list(scandir_it)
|
|
||||||
entries.sort(key=lambda entry: entry.name)
|
|
||||||
return contextlib.nullcontext(entries)
|
|
||||||
|
|
||||||
with mock.patch("os.scandir", my_scandir):
|
|
||||||
self.assertEqual(len(set(base.glob("*"))), 3)
|
|
||||||
subdir.mkdir()
|
|
||||||
self.assertEqual(len(set(base.glob("*"))), 4)
|
|
||||||
subdir.chmod(000)
|
|
||||||
self.assertEqual(len(set(base.glob("*"))), 4)
|
|
||||||
|
|
||||||
@os_helper.skip_unless_symlink
|
@os_helper.skip_unless_symlink
|
||||||
def test_glob_long_symlink(self):
|
def test_glob_long_symlink(self):
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Fixed issue where :meth:`pathlib.Path.glob` returned incomplete results when
|
||||||
|
it encountered a :exc:`PermissionError`. This method now suppresses all
|
||||||
|
:exc:`OSError` exceptions, except those raised from calling
|
||||||
|
:meth:`~pathlib.Path.is_dir` on the top-level path.
|
Loading…
Add table
Add a link
Reference in a new issue