10000 bpo-33635: Handling Bad file descriptor in Path.is_file and related. … · pythoncapi/cpython@2cf3316 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2cf3316

Browse files
bpo-33635: Handling Bad file descriptor in Path.is_file and related. (pythonGH-8542)
(cherry picked from commit 216b745) Co-authored-by: Przemysław Spodymek <przemyslaw@spodymek.com>
1 parent fa3fd4c commit 2cf3316

File tree

3 files changed

+48
-10
lines changed

3 files changed

+48
-10
lines changed

Lib/pathlib.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import re
88
import sys
99
from _collections_abc import Sequence
10-
from errno import EINVAL, ENOENT, ENOTDIR
10+
from errno import EINVAL, ENOENT, ENOTDIR, EBADF
1111
from operator import attrgetter
1212
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
1313
from urllib.parse import quote_from_bytes as urlquote_from_bytes
@@ -34,6 +34,9 @@
3434
# Internals
3535
#
3636

37+
# EBADF - guard agains macOS `stat` throwing EBADF
38+
_IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF)
39+
3740
def _is_wildcard_pattern(pat):
3841
# Whether this pattern needs actual matching using fnmatch, or can
3942
# be looked up directly as a file.
@@ -528,7 +531,13 @@ def _iterate_directories(self, parent_path, is_dir, scandir):
528531
try:
529532
entries = list(scandir(parent_path))
530533
for entry in entries:
531-
if entry.is_dir() and not entry.is_symlink():
534+
entry_is_dir = False
535+
try:
536+
entry_is_dir = entry.is_dir()
537+
except OSError as e:
538+
if e.errno not in _IGNORED_ERROS:
539+
raise
540+
if entry_is_dir and not entry.is_symlink():
532541
path = parent_path._make_child_relpath(entry.name)
533542
for p in self._iterate_directories(path, is_dir, scandir):
534543
yield p
@@ -1319,7 +1328,7 @@ def exists(self):
13191328
try:
13201329
self.stat()
13211330
except OSError as e:
1322-
if e.errno not in (ENOENT, ENOTDIR):
1331+
if e.errno not in _IGNORED_ERROS:
13231332
raise
13241333
return False
13251334
return True
@@ -1331,7 +1340,7 @@ def is_dir(self):
13311340
try:
13321341
return S_ISDIR(self.stat().st_mode)
13331342
except OSError as e:
1334-
if e.errno not in (ENOENT, ENOTDIR):
1343+
if e.errno not in _IGNORED_ERROS:
13351344
raise
13361345
# Path doesn't exist or is a broken symlink
13371346
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
@@ -1345,7 +1354,7 @@ def is_file(self):
13451354
try:
13461355
return S_ISREG(self.stat().st_mode)
13471356
except OSError as e:
1348-
if e.errno not in (ENOENT, ENOTDIR):
1357+
if e.errno not in _IGNORED_ERROS:
13491358
raise
13501359
# Path doesn't exist or is a broken symlink
13511360
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
@@ -1379,7 +1388,7 @@ def is_symlink(self):
13791388
try:
13801389
return S_ISLNK(self.lstat().st_mode)
13811390
except OSError as e:
1382-
if e.errno not in (ENOENT, ENOTDIR):
1391+
if e.errno not in _IGNORED_ERROS:
13831392
raise
13841393
# Path doesn't exist
13851394
return False
@@ -1391,7 +1400,7 @@ def is_block_device(self):
13911400
try:
13921401
return S_ISBLK(self.stat().st_mode)
13931402
except OSError as e:
1394-
if e.errno not in (ENOENT, ENOTDIR):
1403+
if e.errno not in _IGNORED_ERROS:
13951404
raise
13961405
# Path doesn't exist or is a broken symlink
13971406
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
@@ -1404,7 +1413,7 @@ def is_char_device(self):
14041413
try:
14051414
return S_ISCHR(self.stat().st_mode)
14061415
except OSError as e:
1407-
if e.errno not in (ENOENT, ENOTDIR):
1416+
if e.errno not in _IGNORED_ERROS:
14081417
raise
14091418
# Path doesn't exist or is a broken symlink
14101419
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
@@ -1417,7 +1426,7 @@ def is_fifo(self):
14171426
try:
14181427
return S_ISFIFO(self.stat().st_mode)
14191428
except OSError as e:
1420-
if e.errno not in (ENOENT, ENOTDIR):
1429+
if e.errno not in _IGNORED_ERROS:
14211430
raise
14221431
# Path doesn't exist or is a broken symlink
14231432
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
@@ -1430,7 +1439,7 @@ def is_socket(self):
14301439
try:
14311440
return S_ISSOCK(self.stat().st_mode)
14321441
except OSError as e:
1433-
if e.errno not in (ENOENT, ENOTDIR):
1442+
if e.errno not in _IGNORED_ERROS:
14341443
raise
14351444
# Path doesn't exist or is a broken symlink
14361445
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)

Lib/test/test_pathlib.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import collections.abc
22
import io
33
import os
4+
import sys
45
import errno
56
import pathlib
67
import pickle
@@ -2176,6 +2177,29 @@ def test_expanduser(self):
21762177
self.assertEqual(p6.expanduser(), p6)
21772178
self.assertRaises(RuntimeError, p7.expanduser)
21782179

2180+
@unittest.skipIf(sys.platform != "darwin",
2181+
"Bad file descriptor in /dev/fd affects only macOS")
2182+
def test_handling_bad_descriptor(self):
2183+
try:
2184+
file_descriptors = list(pathlib.Path('/dev/fd').rglob("*"))[3:]
2185+
if not file_descriptors:
2186+
self.skipTest("no file descriptors - issue was not reproduced")
2187+
# Checking all file descriptors because there is no guarantee
2188+
# which one will fail.
2189+
for f in file_descriptors:
2190+
f.exists()
2191+
f.is_dir()
2192+
f.is_file()
2193+
f.is_symlink()
2194+
f.is_block_device()
2195+
f.is_char_device()
2196+
f.is_fifo()
2197+
f.is_socket()
2198+
except OSError as e:
2199+
if e.errno == errno.EBADF:
2200+
self.fail("Bad file descriptor not handled.")
2201+
raise
2202+
21792203

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

0 commit comments

Comments
 (0)
0