]> granicus.if.org Git - python/commitdiff
Issue #22054: Add os.get_blocking() and os.set_blocking() functions to get and
authorVictor Stinner <victor.stinner@gmail.com>
Tue, 29 Jul 2014 20:32:47 +0000 (22:32 +0200)
committerVictor Stinner <victor.stinner@gmail.com>
Tue, 29 Jul 2014 20:32:47 +0000 (22:32 +0200)
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.

13 files changed:
Doc/library/os.rst
Include/fileutils.h
Lib/asyncio/unix_events.py
Lib/asyncore.py
Lib/test/test_asyncio/test_unix_events.py
Lib/test/test_io.py
Lib/test/test_os.py
Lib/test/test_posix.py
Lib/test/test_pty.py
Lib/test/test_signal.py
Misc/NEWS
Modules/posixmodule.c
Python/fileutils.c

index c03ce65f21ae826b44096e0bba299189364d2122..9cfc472940842c068c896522d751415ba51c9e28 100644 (file)
@@ -807,6 +807,17 @@ as internal buffering of data.
    Availability: Unix.
 
 
+.. function:: get_blocking(fd)
+
+   Get the blocking mode of the file descriptor: ``False`` if the
+   :data:`O_NONBLOCK` flag is set, ``True`` if the flag is cleared.
+
+   See also :func:`set_blocking` and :meth:`socket.socket.setblocking`.
+
+   Availability: Unix.
+
+   .. versionadded:: 3.5
+
 .. function:: isatty(fd)
 
    Return ``True`` if the file descriptor *fd* is open and connected to a
@@ -1107,6 +1118,18 @@ or `the MSDN <http://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx>`_ on Window
    .. versionadded:: 3.3
 
 
+.. function:: set_blocking(fd, blocking)
+
+   Set the blocking mode of the specified file descriptor. Set the
+   :data:`O_NONBLOCK` flag if blocking is ``False``, clear the flag otherwise.
+
+   See also :func:`get_blocking` and :meth:`socket.socket.setblocking`.
+
+   Availability: Unix.
+
+   .. versionadded:: 3.5
+
+
 .. data:: SF_NODISKIO
           SF_MNOWAIT
           SF_SYNC
index e9bad80b8734ccbc0218b8deaf434828651a4ee8..f2a43f75c4f361f8da7d33ca13e3ce27548acbf6 100644 (file)
@@ -70,7 +70,14 @@ PyAPI_FUNC(int) _Py_set_inheritable(int fd, int inheritable,
                                     int *atomic_flag_works);
 
 PyAPI_FUNC(int) _Py_dup(int fd);
-#endif
+
+#ifndef MS_WINDOWS
+PyAPI_FUNC(int) _Py_get_blocking(int fd);
+
+PyAPI_FUNC(int) _Py_set_blocking(int fd, int blocking);
+#endif   /* !MS_WINDOWS */
+
+#endif   /* Py_LIMITED_API */
 
 #ifdef __cplusplus
 }
index 5020cc5db58e2441c369dbeba8418dfa506aee45..665901ad26379e8c8de184857e7acde82fb55180 100644 (file)
@@ -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 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
         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 @@ class _UnixReadPipeTransport(transports.ReadTransport):
                 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 @@ class _UnixWritePipeTransport(transports._FlowControlMixin,
                 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
index 90854b22bb0108c569d7f2bd01824a91b095a842..da24b3843b3cd4e1bd5dc778f1feeb27fc050fa3 100644 (file)
@@ -590,8 +590,6 @@ def close_all(map=None, ignore_all=False):
 # 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 @@ if os.name == 'posix':
                 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)
index 099d4d51af4af461bd7b2af93647a3ae85791a90..d185533e88e2227190ba42291481bf3be492b0ed 100644 (file)
@@ -306,9 +306,9 @@ class UnixReadPipeTransportTests(test_utils.TestCase):
         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 @@ class UnixWritePipeTransportTests(test_utils.TestCase):
         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()
index 91ba5515447dd6190bad48d3d8d9e68200d342a4..ad86301fcd29492b105020bd4b7db3c0f188c144 100644 (file)
@@ -44,10 +44,6 @@ try:
     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 @@ class MiscIOTest(unittest.TestCase):
                 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
index e669df833842600616fa1104120d8f731f8ee26e..020d0fa41f1a8515f7bfa211006ffdf91a10843e 100644 (file)
@@ -1376,6 +1376,16 @@ class TestInvalidFD(unittest.TestCase):
     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 @@ class FDInheritanceTests(unittest.TestCase):
         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 @@ def test_main():
         CPUCountTests,
         FDInheritanceTests,
         Win32JunctionTests,
+        BlockingTests,
     )
 
 if __name__ == "__main__":
index 3fae2b187cb87d2f16bc8a7c9bb0500b4d830168..d9acfa4fbc9fb1d4ca478484c1e2c0ae30441ef9 100644 (file)
@@ -9,7 +9,6 @@ import errno
 import sys
 import time
 import os
-import fcntl
 import platform
 import pwd
 import shutil
@@ -355,7 +354,7 @@ class PosixTester(unittest.TestCase):
     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 @@ class PosixTester(unittest.TestCase):
         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
index 8916861f5bb465ae8ebdbc2e47e2fe819739fd0b..9b783c376832061b0b4abe3a48994dc7a80567d5 100644 (file)
@@ -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 @@ class PtyTest(unittest.TestCase):
         # 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)
index ca571b9d532e527443dba2fbcf058303be9435b3..caca0be999d9b339306523cdc759580531c208f9 100644 (file)
@@ -276,7 +276,6 @@ class WakeupSignalTests(unittest.TestCase):
         # 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 @@ class WakeupSignalTests(unittest.TestCase):
 
         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 @@ class WakeupSignalTests(unittest.TestCase):
         code = """if 1:
         import _testcapi
         import errno
-        import fcntl
         import os
         import signal
         import sys
@@ -333,8 +328,7 @@ class WakeupSignalTests(unittest.TestCase):
 
         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)
index 2db6da096c856f38a0ba4d0db7ab10fd1aea4b7a..1dd0c478550962045652bff9ea540726927b8382 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -113,6 +113,10 @@ Core and Builtins
 Library
 -------
 
+- 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.
+
 - Issue #17172: Make turtledemo start as active on Mac even when run with
   subprocess.  Patch by Ned Daily and Lita Cho.
 
index b7acbc330c7c8e2f247dbb6bc848464489d6d72c..0bee3c986d0faf4286eb2ef0f93f7fbb01114936 100644 (file)
@@ -11146,6 +11146,56 @@ posix_set_handle_inheritable(PyObject *self, PyObject *args)
 #endif   /* MS_WINDOWS */
 
 
+#ifndef MS_WINDOWS
+PyDoc_STRVAR(get_blocking__doc__,
+    "get_blocking(fd) -> bool\n" \
+    "\n" \
+    "Get the blocking mode of the file descriptor:\n" \
+    "False if the O_NONBLOCK flag is set, True if the flag is cleared.");
+
+static PyObject*
+posix_get_blocking(PyObject *self, PyObject *args)
+{
+    int fd;
+    int blocking;
+
+    if (!PyArg_ParseTuple(args, "i:get_blocking", &fd))
+        return NULL;
+
+    if (!_PyVerify_fd(fd))
+        return posix_error();
+
+    blocking = _Py_get_blocking(fd);
+    if (blocking < 0)
+        return NULL;
+    return PyBool_FromLong(blocking);
+}
+
+PyDoc_STRVAR(set_blocking__doc__,
+    "set_blocking(fd, blocking)\n" \
+    "\n" \
+    "Set the blocking mode of the specified file descriptor.\n" \
+    "Set the O_NONBLOCK flag if blocking is False,\n" \
+    "clear the O_NONBLOCK flag otherwise.");
+
+static PyObject*
+posix_set_blocking(PyObject *self, PyObject *args)
+{
+    int fd, blocking;
+
+    if (!PyArg_ParseTuple(args, "ii:set_blocking", &fd, &blocking))
+        return NULL;
+
+    if (!_PyVerify_fd(fd))
+        return posix_error();
+
+    if (_Py_set_blocking(fd, blocking) < 0)
+        return NULL;
+    Py_RETURN_NONE;
+}
+#endif   /* !MS_WINDOWS */
+
+
 /*[clinic input]
 dump buffer
 [clinic start generated code]*/
@@ -11605,6 +11655,10 @@ static PyMethodDef posix_methods[] = {
      METH_VARARGS, get_handle_inheritable__doc__},
     {"set_handle_inheritable", posix_set_handle_inheritable,
      METH_VARARGS, set_handle_inheritable__doc__},
+#endif
+#ifndef MS_WINDOWS
+    {"get_blocking", posix_get_blocking, METH_VARARGS, get_blocking__doc__},
+    {"set_blocking", posix_set_blocking, METH_VARARGS, set_blocking__doc__},
 #endif
     {NULL,              NULL}            /* Sentinel */
 };
index a55064ffdc238f288941f320b5bfcc84aa09012d..065d3fd974191c1f70536b94fd6eaa1a131e0125 100644 (file)
@@ -1045,3 +1045,56 @@ _Py_dup(int fd)
     return fd;
 }
 
+#ifndef MS_WINDOWS
+/* Get the blocking mode of the file descriptor.
+   Return 0 if the O_NONBLOCK flag is set, 1 if the flag is cleared,
+   raise an exception and return -1 on error. */
+int
+_Py_get_blocking(int fd)
+{
+    int flags = fcntl(fd, F_GETFL, 0);
+    if (flags < 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return -1;
+    }
+
+    return !(flags & O_NONBLOCK);
+}
+
+/* Set the blocking mode of the specified file descriptor.
+
+   Set the O_NONBLOCK flag if blocking is False, clear the O_NONBLOCK flag
+   otherwise.
+
+   Return 0 on success, raise an exception and return -1 on error. */
+int
+_Py_set_blocking(int fd, int blocking)
+{
+#if defined(HAVE_SYS_IOCTL_H) && defined(FIONBIO)
+    int arg = !blocking;
+    if (ioctl(fd, FIONBIO, &arg) < 0)
+        goto error;
+#else
+    int flags, res;
+
+    flags = fcntl(fd, F_GETFL, 0);
+    if (flags < 0)
+        goto error;
+
+    if (blocking)
+        flags = flags & (~O_NONBLOCK);
+    else
+        flags = flags | O_NONBLOCK;
+
+    res = fcntl(fd, F_SETFL, flags);
+    if (res < 0)
+        goto error;
+#endif
+    return 0;
+
+error:
+    PyErr_SetFromErrno(PyExc_OSError);
+    return -1;
+}
+#endif
+