Skip to content

Commit cca0b31

Browse files
bpo-38894: Fix pathlib.Path.glob in the presence of symlinks and insufficient permissions (GH-18815)
Co-authored-by: Matt Wozniski <[email protected]> (cherry picked from commit eb7560a) Co-authored-by: Pablo Galindo <[email protected]>
1 parent 65b0310 commit cca0b31

File tree

3 files changed

+56
-13
lines changed

3 files changed

+56
-13
lines changed

Lib/pathlib.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -524,23 +524,26 @@ def _select_from(self, parent_path, is_dir, exists, scandir):
524524
try:
525525
entries = list(scandir(parent_path))
526526
for entry in entries:
527-
entry_is_dir = False
528-
try:
529-
entry_is_dir = entry.is_dir()
530-
except OSError as e:
531-
if not _ignore_error(e):
532-
raise
533-
if not self.dironly or entry_is_dir:
534-
name = entry.name
535-
if self.match(name):
536-
path = parent_path._make_child_relpath(name)
537-
for p in self.successor._select_from(path, is_dir, exists, scandir):
538-
yield p
527+
if self.dironly:
528+
try:
529+
# "entry.is_dir()" can raise PermissionError
530+
# in some cases (see bpo-38894), which is not
531+
# among the errors ignored by _ignore_error()
532+
if not entry.is_dir():
533+
continue
534+
except OSError as e:
535+
if not _ignore_error(e):
536+
raise
537+
continue
538+
name = entry.name
539+
if self.match(name):
540+
path = parent_path._make_child_relpath(name)
541+
for p in self.successor._select_from(path, is_dir, exists, scandir):
542+
yield p
539543
except PermissionError:
540544
return
541545

542546

543-
544547
class _RecursiveWildcardSelector(_Selector):
545548

546549
def __init__(self, pat, child_parts, flavour):

Lib/test/test_pathlib.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,42 @@ def test_glob_dotdot(self):
14781478
self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") })
14791479
self.assertEqual(set(p.glob("../xyzzy")), set())
14801480

1481+
@support.skip_unless_symlink
1482+
def test_glob_permissions(self):
1483+
# See bpo-38894
1484+
P = self.cls
1485+
base = P(BASE) / 'permissions'
1486+
base.mkdir()
1487+
1488+
file1 = base / "file1"
1489+
file1.touch()
1490+
file2 = base / "file2"
1491+
file2.touch()
1492+
1493+
subdir = base / "subdir"
1494+
1495+
file3 = base / "file3"
1496+
file3.symlink_to(subdir / "other")
1497+
1498+
# Patching is needed to avoid relying on the filesystem
1499+
# to return the order of the files as the error will not
1500+
# happen if the symlink is the last item.
1501+
1502+
with mock.patch("os.scandir") as scandir:
1503+
scandir.return_value = sorted(os.scandir(base))
1504+
self.assertEqual(len(set(base.glob("*"))), 3)
1505+
1506+
subdir.mkdir()
1507+
1508+
with mock.patch("os.scandir") as scandir:
1509+
scandir.return_value = sorted(os.scandir(base))
1510+
self.assertEqual(len(set(base.glob("*"))), 4)
1511+
1512+
subdir.chmod(000)
1513+
1514+
with mock.patch("os.scandir") as scandir:
1515+
scandir.return_value = sorted(os.scandir(base))
1516+
self.assertEqual(len(set(base.glob("*"))), 4)
14811517

14821518
def _check_resolve(self, p, expected, strict=True):
14831519
q = p.resolve(strict)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a bug that was causing incomplete results when calling
2+
``pathlib.Path.glob`` in the presence of symlinks that point
3+
to files where the user does not have read access. Patch by Pablo
4+
Galindo and Matt Wozniski.

0 commit comments

Comments
 (0)