PEP 3151 / issue #12555: reworking the OS and IO exception hierarchy.
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index a9c31d5..0611bd6 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -23,16 +23,8 @@
 # defined in io.py. We don't use real inheritance though, because we don't
 # want to inherit the C implementations.
 
-
-class BlockingIOError(IOError):
-
-    """Exception raised when I/O would block on a non-blocking I/O stream."""
-
-    def __init__(self, errno, strerror, characters_written=0):
-        super().__init__(errno, strerror)
-        if not isinstance(characters_written, int):
-            raise TypeError("characters_written must be a integer")
-        self.characters_written = characters_written
+# Rebind for compatibility
+BlockingIOError = BlockingIOError
 
 
 def open(file, mode="r", buffering=-1, encoding=None, errors=None,
diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py
index 13d3d77..0c96958 100644
--- a/Lib/multiprocessing/connection.py
+++ b/Lib/multiprocessing/connection.py
@@ -321,7 +321,7 @@
                     firstchunk = overlapped.getbuffer()
                     assert lenfirstchunk == len(firstchunk)
                 except IOError as e:
-                    if e.errno == win32.ERROR_BROKEN_PIPE:
+                    if e.winerror == win32.ERROR_BROKEN_PIPE:
                         raise EOFError
                     raise
                 buf.write(firstchunk)
@@ -669,7 +669,7 @@
             try:
                 win32.ConnectNamedPipe(handle, win32.NULL)
             except WindowsError as e:
-                if e.args[0] != win32.ERROR_PIPE_CONNECTED:
+                if e.winerror != win32.ERROR_PIPE_CONNECTED:
                     raise
             return PipeConnection(handle)
 
@@ -692,8 +692,8 @@
                     0, win32.NULL, win32.OPEN_EXISTING, 0, win32.NULL
                     )
             except WindowsError as e:
-                if e.args[0] not in (win32.ERROR_SEM_TIMEOUT,
-                                     win32.ERROR_PIPE_BUSY) or _check_timeout(t):
+                if e.winerror not in (win32.ERROR_SEM_TIMEOUT,
+                                      win32.ERROR_PIPE_BUSY) or _check_timeout(t):
                     raise
             else:
                 break
diff --git a/Lib/test/exception_hierarchy.txt b/Lib/test/exception_hierarchy.txt
index 5037b33..1c1f69f 100644
--- a/Lib/test/exception_hierarchy.txt
+++ b/Lib/test/exception_hierarchy.txt
@@ -11,11 +11,6 @@
       +-- AssertionError
       +-- AttributeError
       +-- BufferError
-      +-- EnvironmentError
-      |    +-- IOError
-      |    +-- OSError
-      |         +-- WindowsError (Windows)
-      |         +-- VMSError (VMS)
       +-- EOFError
       +-- ImportError
       +-- LookupError
@@ -24,6 +19,22 @@
       +-- MemoryError
       +-- NameError
       |    +-- UnboundLocalError
+      +-- OSError
+      |    +-- BlockingIOError
+      |    +-- ChildProcessError
+      |    +-- ConnectionError
+      |    |    +-- BrokenPipeError
+      |    |    +-- ConnectionAbortedError
+      |    |    +-- ConnectionRefusedError
+      |    |    +-- ConnectionResetError
+      |    +-- FileExistsError
+      |    +-- FileNotFoundError
+      |    +-- InterruptedError
+      |    +-- IsADirectoryError
+      |    +-- NotADirectoryError
+      |    +-- PermissionError
+      |    +-- ProcessLookupError
+      |    +-- TimeoutError
       +-- ReferenceError
       +-- RuntimeError
       |    +-- NotImplementedError
diff --git a/Lib/test/test_concurrent_futures.py b/Lib/test/test_concurrent_futures.py
index 78a9906..7522a54 100644
--- a/Lib/test/test_concurrent_futures.py
+++ b/Lib/test/test_concurrent_futures.py
@@ -34,7 +34,7 @@
 RUNNING_FUTURE = create_future(state=RUNNING)
 CANCELLED_FUTURE = create_future(state=CANCELLED)
 CANCELLED_AND_NOTIFIED_FUTURE = create_future(state=CANCELLED_AND_NOTIFIED)
-EXCEPTION_FUTURE = create_future(state=FINISHED, exception=IOError())
+EXCEPTION_FUTURE = create_future(state=FINISHED, exception=OSError())
 SUCCESSFUL_FUTURE = create_future(state=FINISHED, result=42)
 
 
@@ -501,7 +501,7 @@
                          '<Future at 0x[0-9a-f]+ state=cancelled>')
         self.assertRegex(
                 repr(EXCEPTION_FUTURE),
-                '<Future at 0x[0-9a-f]+ state=finished raised IOError>')
+                '<Future at 0x[0-9a-f]+ state=finished raised OSError>')
         self.assertRegex(
                 repr(SUCCESSFUL_FUTURE),
                 '<Future at 0x[0-9a-f]+ state=finished returned int>')
@@ -512,7 +512,7 @@
         f2 = create_future(state=RUNNING)
         f3 = create_future(state=CANCELLED)
         f4 = create_future(state=CANCELLED_AND_NOTIFIED)
-        f5 = create_future(state=FINISHED, exception=IOError())
+        f5 = create_future(state=FINISHED, exception=OSError())
         f6 = create_future(state=FINISHED, result=5)
 
         self.assertTrue(f1.cancel())
@@ -566,7 +566,7 @@
                           CANCELLED_FUTURE.result, timeout=0)
         self.assertRaises(futures.CancelledError,
                           CANCELLED_AND_NOTIFIED_FUTURE.result, timeout=0)
-        self.assertRaises(IOError, EXCEPTION_FUTURE.result, timeout=0)
+        self.assertRaises(OSError, EXCEPTION_FUTURE.result, timeout=0)
         self.assertEqual(SUCCESSFUL_FUTURE.result(timeout=0), 42)
 
     def test_result_with_success(self):
@@ -605,7 +605,7 @@
         self.assertRaises(futures.CancelledError,
                           CANCELLED_AND_NOTIFIED_FUTURE.exception, timeout=0)
         self.assertTrue(isinstance(EXCEPTION_FUTURE.exception(timeout=0),
-                                   IOError))
+                                   OSError))
         self.assertEqual(SUCCESSFUL_FUTURE.exception(timeout=0), None)
 
     def test_exception_with_success(self):
@@ -614,14 +614,14 @@
             time.sleep(1)
             with f1._condition:
                 f1._state = FINISHED
-                f1._exception = IOError()
+                f1._exception = OSError()
                 f1._condition.notify_all()
 
         f1 = create_future(state=PENDING)
         t = threading.Thread(target=notification)
         t.start()
 
-        self.assertTrue(isinstance(f1.exception(timeout=5), IOError))
+        self.assertTrue(isinstance(f1.exception(timeout=5), OSError))
 
 @test.support.reap_threads
 def test_main():
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index 9be6958..a7683ac 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -46,8 +46,8 @@
             fp.close()
             unlink(TESTFN)
 
-        self.raise_catch(IOError, "IOError")
-        self.assertRaises(IOError, open, 'this file does not exist', 'r')
+        self.raise_catch(OSError, "OSError")
+        self.assertRaises(OSError, open, 'this file does not exist', 'r')
 
         self.raise_catch(ImportError, "ImportError")
         self.assertRaises(ImportError, __import__, "undefined_module")
@@ -192,11 +192,35 @@
         except NameError:
             pass
         else:
-            self.assertEqual(str(WindowsError(1001)), "1001")
-            self.assertEqual(str(WindowsError(1001, "message")),
-                             "[Error 1001] message")
-            self.assertEqual(WindowsError(1001, "message").errno, 22)
-            self.assertEqual(WindowsError(1001, "message").winerror, 1001)
+            self.assertIs(WindowsError, OSError)
+            self.assertEqual(str(OSError(1001)), "1001")
+            self.assertEqual(str(OSError(1001, "message")),
+                             "[Errno 1001] message")
+            # POSIX errno (9 aka EBADF) is untranslated
+            w = OSError(9, 'foo', 'bar')
+            self.assertEqual(w.errno, 9)
+            self.assertEqual(w.winerror, None)
+            self.assertEqual(str(w), "[Errno 9] foo: 'bar'")
+            # ERROR_PATH_NOT_FOUND (win error 3) becomes ENOENT (2)
+            w = OSError(0, 'foo', 'bar', 3)
+            self.assertEqual(w.errno, 2)
+            self.assertEqual(w.winerror, 3)
+            self.assertEqual(w.strerror, 'foo')
+            self.assertEqual(w.filename, 'bar')
+            self.assertEqual(str(w), "[Error 3] foo: 'bar'")
+            # Unknown win error becomes EINVAL (22)
+            w = OSError(0, 'foo', None, 1001)
+            self.assertEqual(w.errno, 22)
+            self.assertEqual(w.winerror, 1001)
+            self.assertEqual(w.strerror, 'foo')
+            self.assertEqual(w.filename, None)
+            self.assertEqual(str(w), "[Error 1001] foo")
+            # Non-numeric "errno"
+            w = OSError('bar', 'foo')
+            self.assertEqual(w.errno, 'bar')
+            self.assertEqual(w.winerror, None)
+            self.assertEqual(w.strerror, 'foo')
+            self.assertEqual(w.filename, None)
 
     def testAttributes(self):
         # test that exception attributes are happy
@@ -274,11 +298,12 @@
                  'start' : 0, 'end' : 1}),
         ]
         try:
+            # More tests are in test_WindowsError
             exceptionList.append(
                 (WindowsError, (1, 'strErrorStr', 'filenameStr'),
                     {'args' : (1, 'strErrorStr'),
-                     'strerror' : 'strErrorStr', 'winerror' : 1,
-                     'errno' : 22, 'filename' : 'filenameStr'})
+                     'strerror' : 'strErrorStr', 'winerror' : None,
+                     'errno' : 1, 'filename' : 'filenameStr'})
             )
         except NameError:
             pass
diff --git a/Lib/test/test_http_cookiejar.py b/Lib/test/test_http_cookiejar.py
index 41e0dfd..a35ec95 100644
--- a/Lib/test/test_http_cookiejar.py
+++ b/Lib/test/test_http_cookiejar.py
@@ -248,18 +248,19 @@
         self.assertEqual(c._cookies["www.acme.com"]["/"]["boo"].value, None)
 
     def test_bad_magic(self):
-        # IOErrors (eg. file doesn't exist) are allowed to propagate
+        # OSErrors (eg. file doesn't exist) are allowed to propagate
         filename = test.support.TESTFN
         for cookiejar_class in LWPCookieJar, MozillaCookieJar:
             c = cookiejar_class()
             try:
                 c.load(filename="for this test to work, a file with this "
                                 "filename should not exist")
-            except IOError as exc:
-                # exactly IOError, not LoadError
-                self.assertIs(exc.__class__, IOError)
+            except OSError as exc:
+                # an OSError subclass (likely FileNotFoundError), but not
+                # LoadError
+                self.assertIsNot(exc.__class__, LoadError)
             else:
-                self.fail("expected IOError for invalid filename")
+                self.fail("expected OSError for invalid filename")
         # Invalid contents of cookies file (eg. bad magic string)
         # causes a LoadError.
         try:
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index a9e3c37..0debc80 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -2643,12 +2643,6 @@
 
     def test_blockingioerror(self):
         # Various BlockingIOError issues
-        self.assertRaises(TypeError, self.BlockingIOError)
-        self.assertRaises(TypeError, self.BlockingIOError, 1)
-        self.assertRaises(TypeError, self.BlockingIOError, 1, 2, 3, 4)
-        self.assertRaises(TypeError, self.BlockingIOError, 1, "", None)
-        b = self.BlockingIOError(1, "")
-        self.assertEqual(b.characters_written, 0)
         class C(str):
             pass
         c = C("")
diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py
index 1cfcee6..2230028 100644
--- a/Lib/test/test_mmap.py
+++ b/Lib/test/test_mmap.py
@@ -563,8 +563,7 @@
         f.close()
 
     def test_error(self):
-        self.assertTrue(issubclass(mmap.error, EnvironmentError))
-        self.assertIn("mmap.error", str(mmap.error))
+        self.assertIs(mmap.error, OSError)
 
     def test_io_methods(self):
         data = b"0123456789"
diff --git a/Lib/test/test_pep3151.py b/Lib/test/test_pep3151.py
new file mode 100644
index 0000000..9d92425
--- /dev/null
+++ b/Lib/test/test_pep3151.py
@@ -0,0 +1,128 @@
+import builtins
+import os
+import select
+import socket
+import sys
+import unittest
+import errno
+from errno import EEXIST
+
+from test import support
+
+class SubOSError(OSError):
+    pass
+
+
+class HierarchyTest(unittest.TestCase):
+
+    def test_builtin_errors(self):
+        self.assertEqual(OSError.__name__, 'OSError')
+        self.assertIs(IOError, OSError)
+        self.assertIs(EnvironmentError, OSError)
+
+    def test_socket_errors(self):
+        self.assertIs(socket.error, IOError)
+        self.assertIs(socket.gaierror.__base__, OSError)
+        self.assertIs(socket.herror.__base__, OSError)
+        self.assertIs(socket.timeout.__base__, OSError)
+
+    def test_select_error(self):
+        self.assertIs(select.error, OSError)
+
+    # mmap.error is tested in test_mmap
+
+    _pep_map = """
+        +-- BlockingIOError        EAGAIN, EALREADY, EWOULDBLOCK, EINPROGRESS
+        +-- ChildProcessError                                          ECHILD
+        +-- ConnectionError
+            +-- BrokenPipeError                              EPIPE, ESHUTDOWN
+            +-- ConnectionAbortedError                           ECONNABORTED
+            +-- ConnectionRefusedError                           ECONNREFUSED
+            +-- ConnectionResetError                               ECONNRESET
+        +-- FileExistsError                                            EEXIST
+        +-- FileNotFoundError                                          ENOENT
+        +-- InterruptedError                                            EINTR
+        +-- IsADirectoryError                                          EISDIR
+        +-- NotADirectoryError                                        ENOTDIR
+        +-- PermissionError                                     EACCES, EPERM
+        +-- ProcessLookupError                                          ESRCH
+        +-- TimeoutError                                            ETIMEDOUT
+    """
+    def _make_map(s):
+        _map = {}
+        for line in s.splitlines():
+            line = line.strip('+- ')
+            if not line:
+                continue
+            excname, _, errnames = line.partition(' ')
+            for errname in filter(None, errnames.strip().split(', ')):
+                _map[getattr(errno, errname)] = getattr(builtins, excname)
+        return _map
+    _map = _make_map(_pep_map)
+
+    def test_errno_mapping(self):
+        # The OSError constructor maps errnos to subclasses
+        # A sample test for the basic functionality
+        e = OSError(EEXIST, "Bad file descriptor")
+        self.assertIs(type(e), FileExistsError)
+        # Exhaustive testing
+        for errcode, exc in self._map.items():
+            e = OSError(errcode, "Some message")
+            self.assertIs(type(e), exc)
+        othercodes = set(errno.errorcode) - set(self._map)
+        for errcode in othercodes:
+            e = OSError(errcode, "Some message")
+            self.assertIs(type(e), OSError)
+
+    def test_OSError_subclass_mapping(self):
+        # When constructing an OSError subclass, errno mapping isn't done
+        e = SubOSError(EEXIST, "Bad file descriptor")
+        self.assertIs(type(e), SubOSError)
+
+
+class AttributesTest(unittest.TestCase):
+
+    def test_windows_error(self):
+        if os.name == "nt":
+            self.assertIn('winerror', dir(OSError))
+        else:
+            self.assertNotIn('winerror', dir(OSError))
+
+    def test_posix_error(self):
+        e = OSError(EEXIST, "File already exists", "foo.txt")
+        self.assertEqual(e.errno, EEXIST)
+        self.assertEqual(e.args[0], EEXIST)
+        self.assertEqual(e.strerror, "File already exists")
+        self.assertEqual(e.filename, "foo.txt")
+        if os.name == "nt":
+            self.assertEqual(e.winerror, None)
+
+    @unittest.skipUnless(os.name == "nt", "Windows-specific test")
+    def test_errno_translation(self):
+        # ERROR_ALREADY_EXISTS (183) -> EEXIST
+        e = OSError(0, "File already exists", "foo.txt", 183)
+        self.assertEqual(e.winerror, 183)
+        self.assertEqual(e.errno, EEXIST)
+        self.assertEqual(e.args[0], EEXIST)
+        self.assertEqual(e.strerror, "File already exists")
+        self.assertEqual(e.filename, "foo.txt")
+
+    def test_blockingioerror(self):
+        args = ("a", "b", "c", "d", "e")
+        for n in range(6):
+            e = BlockingIOError(*args[:n])
+            with self.assertRaises(AttributeError):
+                e.characters_written
+        e = BlockingIOError("a", "b", 3)
+        self.assertEqual(e.characters_written, 3)
+        e.characters_written = 5
+        self.assertEqual(e.characters_written, 5)
+
+    # XXX VMSError not tested
+
+
+def test_main():
+    support.run_unittest(__name__)
+
+if __name__=="__main__":
+    test_main()
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
index 40c2291..4dd8d22 100644
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -1339,7 +1339,7 @@
     try:
         data = XINCLUDE[href]
     except KeyError:
-        raise IOError("resource not found")
+        raise OSError("resource not found")
     if parse == "xml":
         from xml.etree.ElementTree import XML
         return XML(data)
@@ -1404,7 +1404,7 @@
     >>> document = xinclude_loader("C5.xml")
     >>> ElementInclude.include(document, xinclude_loader)
     Traceback (most recent call last):
-    IOError: resource not found
+    OSError: resource not found
     >>> # print(serialize(document)) # C5
     """
 
@@ -1611,7 +1611,7 @@
 
 class ExceptionFile:
     def read(self, x):
-        raise IOError
+        raise OSError
 
 def xmltoolkit60():
     """
@@ -1619,7 +1619,7 @@
     Handle crash in stream source.
     >>> tree = ET.parse(ExceptionFile())
     Traceback (most recent call last):
-    IOError
+    OSError
 
     """
 
diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py
index 671ab68..a947608 100644
--- a/Lib/urllib/request.py
+++ b/Lib/urllib/request.py
@@ -1547,6 +1547,8 @@
                 return getattr(self, name)(url)
             else:
                 return getattr(self, name)(url, data)
+        except HTTPError:
+            raise
         except socket.error as msg:
             raise IOError('socket error', msg).with_traceback(sys.exc_info()[2])