[Python-checkins] GH-90208: Suppress OSError exceptions from `pathlib.Path.glob()` (GH-104141)

barneygale webhook-mailer at python.org
Wed May 10 20:02:03 EDT 2023


https://github.com/python/cpython/commit/94f30c75576bb8a20724b2ac758fa33af089a522
commit: 94f30c75576bb8a20724b2ac758fa33af089a522
branch: main
author: Barney Gale <barney.gale at gmail.com>
committer: barneygale <barney.gale at gmail.com>
date: 2023-05-11T01:01:39+01:00
summary:

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.

files:
A Misc/NEWS.d/next/Library/2023-05-03-19-22-24.gh-issue-90208.tI00da.rst
M Lib/pathlib.py
M Lib/test/test_pathlib.py

diff --git a/Lib/pathlib.py b/Lib/pathlib.py
index 25863c76f308..40b72930e1f0 100644
--- a/Lib/pathlib.py
+++ b/Lib/pathlib.py
@@ -142,25 +142,21 @@ def _select_from(self, parent_path, scandir):
             # avoid exhausting file descriptors when globbing deep trees.
             with scandir(parent_path) as scandir_it:
                 entries = list(scandir_it)
+        except OSError:
+            pass
+        else:
             for entry in entries:
                 if self.dironly:
                     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():
                             continue
-                    except OSError as e:
-                        if not _ignore_error(e):
-                            raise
+                    except OSError:
                         continue
                 name = entry.name
                 if self.match(name):
                     path = parent_path._make_child_relpath(name)
                     for p in self.successor._select_from(path, scandir):
                         yield p
-        except PermissionError:
-            return
 
 
 class _RecursiveWildcardSelector(_Selector):
@@ -175,28 +171,25 @@ def _iterate_directories(self, parent_path, scandir):
             # avoid exhausting file descriptors when globbing deep trees.
             with scandir(parent_path) as scandir_it:
                 entries = list(scandir_it)
+        except OSError:
+            pass
+        else:
             for entry in entries:
                 entry_is_dir = False
                 try:
                     entry_is_dir = entry.is_dir(follow_symlinks=False)
-                except OSError as e:
-                    if not _ignore_error(e):
-                        raise
+                except OSError:
+                    pass
                 if entry_is_dir:
                     path = parent_path._make_child_relpath(entry.name)
                     for p in self._iterate_directories(path, scandir):
                         yield p
-        except PermissionError:
-            return
 
     def _select_from(self, parent_path, scandir):
-        try:
-            successor_select = self.successor._select_from
-            for starting_point in self._iterate_directories(parent_path, scandir):
-                for p in successor_select(starting_point, scandir):
-                    yield p
-        except PermissionError:
-            return
+        successor_select = self.successor._select_from
+        for starting_point in self._iterate_directories(parent_path, scandir):
+            for p in successor_select(starting_point, scandir):
+                yield p
 
 
 class _DoubleRecursiveWildcardSelector(_RecursiveWildcardSelector):
diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py
index 10dd604138a6..67ca4795962b 100644
--- a/Lib/test/test_pathlib.py
+++ b/Lib/test/test_pathlib.py
@@ -1949,33 +1949,19 @@ def test_glob_permissions(self):
         P = self.cls
         base = P(BASE) / 'permissions'
         base.mkdir()
+        self.addCleanup(os_helper.rmtree, base)
 
-        file1 = base / "file1"
-        file1.touch()
-        file2 = base / "file2"
-        file2.touch()
-
-        subdir = base / "subdir"
-
-        file3 = base / "file3"
-        file3.symlink_to(subdir / "other")
-
-        # 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)
+        for i in range(100):
+            link = base / f"link{i}"
+            if i % 2:
+                link.symlink_to(P(BASE, "dirE", "nonexistent"))
+            else:
+                link.symlink_to(P(BASE, "dirC"))
+
+        self.assertEqual(len(set(base.glob("*"))), 100)
+        self.assertEqual(len(set(base.glob("*/"))), 50)
+        self.assertEqual(len(set(base.glob("*/fileC"))), 50)
+        self.assertEqual(len(set(base.glob("*/file*"))), 50)
 
     @os_helper.skip_unless_symlink
     def test_glob_long_symlink(self):
diff --git a/Misc/NEWS.d/next/Library/2023-05-03-19-22-24.gh-issue-90208.tI00da.rst b/Misc/NEWS.d/next/Library/2023-05-03-19-22-24.gh-issue-90208.tI00da.rst
new file mode 100644
index 000000000000..1fd9588bebd5
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-05-03-19-22-24.gh-issue-90208.tI00da.rst
@@ -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.



More information about the Python-checkins mailing list