Skip to content

Commit 94f30c7

Browse files
authored
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.
1 parent 373bca0 commit 94f30c7

File tree

3 files changed

+29
-46
lines changed

3 files changed

+29
-46
lines changed

Lib/pathlib.py

+13-20
Original file line numberDiff line numberDiff line change
@@ -142,25 +142,21 @@ def _select_from(self, parent_path, scandir):
142142
# avoid exhausting file descriptors when globbing deep trees.
143143
with scandir(parent_path) as scandir_it:
144144
entries = list(scandir_it)
145+
except OSError:
146+
pass
147+
else:
145148
for entry in entries:
146149
if self.dironly:
147150
try:
148-
# "entry.is_dir()" can raise PermissionError
149-
# in some cases (see bpo-38894), which is not
150-
# among the errors ignored by _ignore_error()
151151
if not entry.is_dir():
152152
continue
153-
except OSError as e:
154-
if not _ignore_error(e):
155-
raise
153+
except OSError:
156154
continue
157155
name = entry.name
158156
if self.match(name):
159157
path = parent_path._make_child_relpath(name)
160158
for p in self.successor._select_from(path, scandir):
161159
yield p
162-
except PermissionError:
163-
return
164160

165161

166162
class _RecursiveWildcardSelector(_Selector):
@@ -175,28 +171,25 @@ def _iterate_directories(self, parent_path, scandir):
175171
# avoid exhausting file descriptors when globbing deep trees.
176172
with scandir(parent_path) as scandir_it:
177173
entries = list(scandir_it)
174+
except OSError:
175+
pass
176+
else:
178177
for entry in entries:
179178
entry_is_dir = False
180179
try:
181180
entry_is_dir = entry.is_dir(follow_symlinks=False)
182-
except OSError as e:
183-
if not _ignore_error(e):
184-
raise
181+
except OSError:
182+
pass
185183
if entry_is_dir:
186184
path = parent_path._make_child_relpath(entry.name)
187185
for p in self._iterate_directories(path, scandir):
188186
yield p
189-
except PermissionError:
190-
return
191187

192188
def _select_from(self, parent_path, scandir):
193-
try:
194-
successor_select = self.successor._select_from
195-
for starting_point in self._iterate_directories(parent_path, scandir):
196-
for p in successor_select(starting_point, scandir):
197-
yield p
198-
except PermissionError:
199-
return
189+
successor_select = self.successor._select_from
190+
for starting_point in self._iterate_directories(parent_path, scandir):
191+
for p in successor_select(starting_point, scandir):
192+
yield p
200193

201194

202195
class _DoubleRecursiveWildcardSelector(_RecursiveWildcardSelector):

Lib/test/test_pathlib.py

+12-26
Original file line numberDiff line numberDiff line change
@@ -1949,33 +1949,19 @@ def test_glob_permissions(self):
19491949
P = self.cls
19501950
base = P(BASE) / 'permissions'
19511951
base.mkdir()
1952+
self.addCleanup(os_helper.rmtree, base)
19521953

1953-
file1 = base / "file1"
1954-
file1.touch()
1955-
file2 = base / "file2"
1956-
file2.touch()
1957-
1958-
subdir = base / "subdir"
1959-
1960-
file3 = base / "file3"
1961-
file3.symlink_to(subdir / "other")
1962-
1963-
# Patching is needed to avoid relying on the filesystem
1964-
# to return the order of the files as the error will not
1965-
# happen if the symlink is the last item.
1966-
real_scandir = os.scandir
1967-
def my_scandir(path):
1968-
with real_scandir(path) as scandir_it:
1969-
entries = list(scandir_it)
1970-
entries.sort(key=lambda entry: entry.name)
1971-
return contextlib.nullcontext(entries)
1972-
1973-
with mock.patch("os.scandir", my_scandir):
1974-
self.assertEqual(len(set(base.glob("*"))), 3)
1975-
subdir.mkdir()
1976-
self.assertEqual(len(set(base.glob("*"))), 4)
1977-
subdir.chmod(000)
1978-
self.assertEqual(len(set(base.glob("*"))), 4)
1954+
for i in range(100):
1955+
link = base / f"link{i}"
1956+
if i % 2:
1957+
link.symlink_to(P(BASE, "dirE", "nonexistent"))
1958+
else:
1959+
link.symlink_to(P(BASE, "dirC"))
1960+
1961+
self.assertEqual(len(set(base.glob("*"))), 100)
1962+
self.assertEqual(len(set(base.glob("*/"))), 50)
1963+
self.assertEqual(len(set(base.glob("*/fileC"))), 50)
1964+
self.assertEqual(len(set(base.glob("*/file*"))), 50)
19791965

19801966
@os_helper.skip_unless_symlink
19811967
def test_glob_long_symlink(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fixed issue where :meth:`pathlib.Path.glob` returned incomplete results when
2+
it encountered a :exc:`PermissionError`. This method now suppresses all
3+
:exc:`OSError` exceptions, except those raised from calling
4+
:meth:`~pathlib.Path.is_dir` on the top-level path.

0 commit comments

Comments
 (0)