Issue #22054: Add os.get_blocking() and os.set_blocking() functions to get and
set the blocking mode of a file descriptor (False if the O_NONBLOCK flag is
set, True otherwise). These functions are not available on Windows.
diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py
index 5020cc5..665901a 100644
--- a/Lib/asyncio/unix_events.py
+++ b/Lib/asyncio/unix_events.py
@@ -1,7 +1,6 @@
 """Selector event loop for Unix with signal handling."""
 
 import errno
-import fcntl
 import os
 import signal
 import socket
@@ -259,12 +258,6 @@
         return server
 
 
-def _set_nonblocking(fd):
-    flags = fcntl.fcntl(fd, fcntl.F_GETFL)
-    flags = flags | os.O_NONBLOCK
-    fcntl.fcntl(fd, fcntl.F_SETFL, flags)
-
-
 class _UnixReadPipeTransport(transports.ReadTransport):
 
     max_size = 256 * 1024  # max bytes we read in one event loop iteration
@@ -280,7 +273,7 @@
                 stat.S_ISSOCK(mode) or
                 stat.S_ISCHR(mode)):
             raise ValueError("Pipe transport is for pipes/sockets only.")
-        _set_nonblocking(self._fileno)
+        os.set_blocking(self._fileno, False)
         self._protocol = protocol
         self._closing = False
         self._loop.add_reader(self._fileno, self._read_ready)
@@ -373,7 +366,7 @@
                 stat.S_ISCHR(mode)):
             raise ValueError("Pipe transport is only for "
                              "pipes, sockets and character devices")
-        _set_nonblocking(self._fileno)
+        os.set_blocking(self._fileno, False)
         self._protocol = protocol
         self._buffer = []
         self._conn_lost = 0
diff --git a/Lib/asyncore.py b/Lib/asyncore.py
index 90854b2..da24b38 100644
--- a/Lib/asyncore.py
+++ b/Lib/asyncore.py
@@ -590,8 +590,6 @@
 # Regardless, this is useful for pipes, and stdin/stdout...
 
 if os.name == 'posix':
-    import fcntl
-
     class file_wrapper:
         # Here we override just enough to make a file
         # look like a socket for the purposes of asyncore.
@@ -642,9 +640,7 @@
                 pass
             self.set_file(fd)
             # set it to non-blocking mode
-            flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
-            flags = flags | os.O_NONBLOCK
-            fcntl.fcntl(fd, fcntl.F_SETFL, flags)
+            os.set_blocking(fd, False)
 
         def set_file(self, fd):
             self.socket = file_wrapper(fd)
diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py
index 099d4d5..d185533 100644
--- a/Lib/test/test_asyncio/test_unix_events.py
+++ b/Lib/test/test_asyncio/test_unix_events.py
@@ -306,9 +306,9 @@
         self.pipe = mock.Mock(spec_set=io.RawIOBase)
         self.pipe.fileno.return_value = 5
 
-        fcntl_patcher = mock.patch('fcntl.fcntl')
-        fcntl_patcher.start()
-        self.addCleanup(fcntl_patcher.stop)
+        blocking_patcher = mock.patch('os.set_blocking')
+        blocking_patcher.start()
+        self.addCleanup(blocking_patcher.stop)
 
         fstat_patcher = mock.patch('os.fstat')
         m_fstat = fstat_patcher.start()
@@ -469,9 +469,9 @@
         self.pipe = mock.Mock(spec_set=io.RawIOBase)
         self.pipe.fileno.return_value = 5
 
-        fcntl_patcher = mock.patch('fcntl.fcntl')
-        fcntl_patcher.start()
-        self.addCleanup(fcntl_patcher.stop)
+        blocking_patcher = mock.patch('os.set_blocking')
+        blocking_patcher.start()
+        self.addCleanup(blocking_patcher.stop)
 
         fstat_patcher = mock.patch('os.fstat')
         m_fstat = fstat_patcher.start()
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index 91ba551..ad86301 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -44,10 +44,6 @@
     import threading
 except ImportError:
     threading = None
-try:
-    import fcntl
-except ImportError:
-    fcntl = None
 
 def _default_chunk_size():
     """Get the default TextIOWrapper chunk size"""
@@ -3230,26 +3226,20 @@
                 with self.open(support.TESTFN, **kwargs) as f:
                     self.assertRaises(TypeError, pickle.dumps, f, protocol)
 
-    @unittest.skipUnless(fcntl, 'fcntl required for this test')
     def test_nonblock_pipe_write_bigbuf(self):
         self._test_nonblock_pipe_write(16*1024)
 
-    @unittest.skipUnless(fcntl, 'fcntl required for this test')
     def test_nonblock_pipe_write_smallbuf(self):
         self._test_nonblock_pipe_write(1024)
 
-    def _set_non_blocking(self, fd):
-        flags = fcntl.fcntl(fd, fcntl.F_GETFL)
-        self.assertNotEqual(flags, -1)
-        res = fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
-        self.assertEqual(res, 0)
-
+    @unittest.skipUnless(hasattr(os, 'set_blocking'),
+                         'os.set_blocking() required for this test')
     def _test_nonblock_pipe_write(self, bufsize):
         sent = []
         received = []
         r, w = os.pipe()
-        self._set_non_blocking(r)
-        self._set_non_blocking(w)
+        os.set_blocking(r, False)
+        os.set_blocking(w, False)
 
         # To exercise all code paths in the C implementation we need
         # to play with buffer sizes.  For instance, if we choose a
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index e669df8..020d0fa 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -1376,6 +1376,16 @@
     def test_writev(self):
         self.check(os.writev, [b'abc'])
 
+    def test_inheritable(self):
+        self.check(os.get_inheritable)
+        self.check(os.set_inheritable, True)
+
+    @unittest.skipUnless(hasattr(os, 'get_blocking'),
+                         'needs os.get_blocking() and os.set_blocking()')
+    def test_blocking(self):
+        self.check(os.get_blocking)
+        self.check(os.set_blocking, True)
+
 
 class LinkTests(unittest.TestCase):
     def setUp(self):
@@ -2591,6 +2601,21 @@
         self.assertEqual(os.get_inheritable(slave_fd), False)
 
 
+@unittest.skipUnless(hasattr(os, 'get_blocking'),
+                     'needs os.get_blocking() and os.set_blocking()')
+class BlockingTests(unittest.TestCase):
+    def test_blocking(self):
+        fd = os.open(__file__, os.O_RDONLY)
+        self.addCleanup(os.close, fd)
+        self.assertEqual(os.get_blocking(fd), True)
+
+        os.set_blocking(fd, False)
+        self.assertEqual(os.get_blocking(fd), False)
+
+        os.set_blocking(fd, True)
+        self.assertEqual(os.get_blocking(fd), True)
+
+
 @support.reap_threads
 def test_main():
     support.run_unittest(
@@ -2626,6 +2651,7 @@
         CPUCountTests,
         FDInheritanceTests,
         Win32JunctionTests,
+        BlockingTests,
     )
 
 if __name__ == "__main__":
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index 3fae2b1..d9acfa4 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -9,7 +9,6 @@
 import sys
 import time
 import os
-import fcntl
 import platform
 import pwd
 import shutil
@@ -355,7 +354,7 @@
     def test_oscloexec(self):
         fd = os.open(support.TESTFN, os.O_RDONLY|os.O_CLOEXEC)
         self.addCleanup(os.close, fd)
-        self.assertTrue(fcntl.fcntl(fd, fcntl.F_GETFD) & fcntl.FD_CLOEXEC)
+        self.assertFalse(os.get_inheritable(fd))
 
     @unittest.skipUnless(hasattr(posix, 'O_EXLOCK'),
                          'test needs posix.O_EXLOCK')
@@ -605,8 +604,8 @@
         self.addCleanup(os.close, w)
         self.assertFalse(os.get_inheritable(r))
         self.assertFalse(os.get_inheritable(w))
-        self.assertTrue(fcntl.fcntl(r, fcntl.F_GETFL) & os.O_NONBLOCK)
-        self.assertTrue(fcntl.fcntl(w, fcntl.F_GETFL) & os.O_NONBLOCK)
+        self.assertFalse(os.get_blocking(r))
+        self.assertFalse(os.get_blocking(w))
         # try reading from an empty pipe: this should fail, not block
         self.assertRaises(OSError, os.read, r, 1)
         # try a write big enough to fill-up the pipe: this should either
diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py
index 8916861..9b783c3 100644
--- a/Lib/test/test_pty.py
+++ b/Lib/test/test_pty.py
@@ -1,7 +1,6 @@
 from test.support import verbose, run_unittest, import_module, reap_children
 
-#Skip these tests if either fcntl or termios is not available
-fcntl = import_module('fcntl')
+# Skip these tests if termios is not available
 import_module('termios')
 
 import errno
@@ -84,16 +83,18 @@
         # in master_open(), we need to read the EOF.
 
         # Ensure the fd is non-blocking in case there's nothing to read.
-        orig_flags = fcntl.fcntl(master_fd, fcntl.F_GETFL)
-        fcntl.fcntl(master_fd, fcntl.F_SETFL, orig_flags | os.O_NONBLOCK)
+        blocking = os.get_blocking(master_fd)
         try:
-            s1 = os.read(master_fd, 1024)
-            self.assertEqual(b'', s1)
-        except OSError as e:
-            if e.errno != errno.EAGAIN:
-                raise
-        # Restore the original flags.
-        fcntl.fcntl(master_fd, fcntl.F_SETFL, orig_flags)
+            os.set_blocking(master_fd, False)
+            try:
+                s1 = os.read(master_fd, 1024)
+                self.assertEqual(b'', s1)
+            except OSError as e:
+                if e.errno != errno.EAGAIN:
+                    raise
+        finally:
+            # Restore the original flags.
+            os.set_blocking(master_fd, blocking)
 
         debug("Writing to slave_fd")
         os.write(slave_fd, TEST_STRING_1)
diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py
index ca571b9..caca0be 100644
--- a/Lib/test/test_signal.py
+++ b/Lib/test/test_signal.py
@@ -276,7 +276,6 @@
         # use a subprocess to have only one thread
         code = """if 1:
         import _testcapi
-        import fcntl
         import os
         import signal
         import struct
@@ -299,10 +298,7 @@
 
         signal.signal(signal.SIGALRM, handler)
         read, write = os.pipe()
-        for fd in (read, write):
-            flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
-            flags = flags | os.O_NONBLOCK
-            fcntl.fcntl(fd, fcntl.F_SETFL, flags)
+        os.set_blocking(write, False)
         signal.set_wakeup_fd(write)
 
         test()
@@ -322,7 +318,6 @@
         code = """if 1:
         import _testcapi
         import errno
-        import fcntl
         import os
         import signal
         import sys
@@ -333,8 +328,7 @@
 
         signal.signal(signal.SIGALRM, handler)
         r, w = os.pipe()
-        flags = fcntl.fcntl(r, fcntl.F_GETFL, 0)
-        fcntl.fcntl(r, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+        os.set_blocking(r, False)
 
         # Set wakeup_fd a read-only file descriptor to trigger the error
         signal.set_wakeup_fd(r)