From 216b745eafa7cd4a683a8405dcfbd7f5567f504c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Spodymek?= Date: Mon, 27 Aug 2018 23:33:45 +0200 Subject: [PATCH] bpo-33635: Handling Bad file descriptor in Path.is_file and related. (GH-8542) --- Lib/pathlib.py | 29 ++++++++++++------- Lib/test/test_pathlib.py | 24 +++++++++++++++ .../2018-07-31-09-51-01.bpo-33635.KiscE-.rst | 5 ++++ 3 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/macOS/2018-07-31-09-51-01.bpo-33635.KiscE-.rst diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 6be372ed320..c2986bd022d 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -7,7 +7,7 @@ import posixpath 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 @@ -34,6 +34,9 @@ __all__ = [ # 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. @@ -528,7 +531,13 @@ class _RecursiveWildcardSelector(_Selector): 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 @@ -1319,7 +1328,7 @@ class Path(PurePath): 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 @@ -1331,7 +1340,7 @@ class Path(PurePath): 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/) @@ -1345,7 +1354,7 @@ class Path(PurePath): 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/) @@ -1379,7 +1388,7 @@ class Path(PurePath): 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 @@ -1391,7 +1400,7 @@ class Path(PurePath): 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/) @@ -1404,7 +1413,7 @@ class Path(PurePath): 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/) @@ -1417,7 +1426,7 @@ class Path(PurePath): 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/) @@ -1430,7 +1439,7 @@ class Path(PurePath): 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/) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index ae7c75deb0e..e436db995ce 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1,6 +1,7 @@ import collections.abc import io import os +import sys import errno import pathlib import pickle @@ -2176,6 +2177,29 @@ class PosixPathTest(_BasePathTest, unittest.TestCase): 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): diff --git a/Misc/NEWS.d/next/macOS/2018-07-31-09-51-01.bpo-33635.KiscE-.rst b/Misc/NEWS.d/next/macOS/2018-07-31-09-51-01.bpo-33635.KiscE-.rst new file mode 100644 index 00000000000..90d9ab6d420 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2018-07-31-09-51-01.bpo-33635.KiscE-.rst @@ -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.