Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import re
import sys
from _collections_abc import Sequence
from errno import EINVAL, ENOENT, ENOTDIR
from errno import EINVAL, ENOENT, ENOTDIR, EBADF
from operator import attrgetter
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
from urllib.parse import quote_from_bytes as urlquote_from_bytes
Expand All @@ -34,6 +34,9 @@
# Internals
#

# EBADF - guard agains macOS `stat` throwing EBADF
_IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF)

def _is_wildcard_pattern(pat):
# Whether this pattern needs actual matching using fnmatch, or can
# be looked up directly as a file.
Expand Down Expand Up @@ -528,7 +531,13 @@ def _iterate_directories(self, parent_path, is_dir, scandir):
try:
entries = list(scandir(parent_path))
for entry in entries:
if entry.is_dir() and not entry.is_symlink():
entry_is_dir = False
try:
entry_is_dir = entry.is_dir()
except OSError as e:
if e.errno not in _IGNORED_ERROS:
raise
if entry_is_dir and not entry.is_symlink():
path = parent_path._make_child_relpath(entry.name)
for p in self._iterate_directories(path, is_dir, scandir):
yield p
Expand Down Expand Up @@ -1319,7 +1328,7 @@ def exists(self):
try:
self.stat()
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if e.errno not in _IGNORED_ERROS:
raise
return False
return True
Expand All @@ -1331,7 +1340,7 @@ def is_dir(self):
try:
return S_ISDIR(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if e.errno not in _IGNORED_ERROS:
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
Expand All @@ -1345,7 +1354,7 @@ def is_file(self):
try:
return S_ISREG(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if e.errno not in _IGNORED_ERROS:
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
Expand Down Expand Up @@ -1379,7 +1388,7 @@ def is_symlink(self):
try:
return S_ISLNK(self.lstat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if e.errno not in _IGNORED_ERROS:
raise
# Path doesn't exist
return False
Expand All @@ -1391,7 +1400,7 @@ def is_block_device(self):
try:
return S_ISBLK(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if e.errno not in _IGNORED_ERROS:
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
Expand All @@ -1404,7 +1413,7 @@ def is_char_device(self):
try:
return S_ISCHR(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if e.errno not in _IGNORED_ERROS:
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
Expand All @@ -1417,7 +1426,7 @@ def is_fifo(self):
try:
return S_ISFIFO(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if e.errno not in _IGNORED_ERROS:
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
Expand All @@ -1430,7 +1439,7 @@ def is_socket(self):
try:
return S_ISSOCK(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if e.errno not in _IGNORED_ERROS:
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import collections.abc
import io
import os
import sys
import errno
import pathlib
import pickle
Expand Down Expand Up @@ -2176,6 +2177,29 @@ def test_expanduser(self):
self.assertEqual(p6.expanduser(), p6)
self.assertRaises(RuntimeError, p7.expanduser)

@unittest.skipIf(sys.platform != "darwin",
"Bad file descriptor in /dev/fd affects only macOS")
def test_handling_bad_descriptor(self):
try:
file_descriptors = list(pathlib.Path('/dev/fd').rglob("*"))[3:]
if not file_descriptors:
self.skipTest("no file descriptors - issue was not reproduced")
# Checking all file descriptors because there is no guarantee
# which one will fail.
for f in file_descriptors:
f.exists()
f.is_dir()
f.is_file()
f.is_symlink()
f.is_block_device()
f.is_char_device()
f.is_fifo()
f.is_socket()
except OSError as e:
if e.errno == errno.EBADF:
self.fail("Bad file descriptor not handled.")
raise


@only_nt
class WindowsPathTest(_BasePathTest, unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
In macOS stat on some file descriptors (/dev/fd/3 f.e) will result in bad
file descriptor OSError. Guard against this exception was added in is_dir,
is_file and similar methods. DirEntry.is_dir can also throw this exception
so _RecursiveWildcardSelector._iterate_directories was also extended with
the same error ignoring pattern.