summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/os.rst23
-rw-r--r--Include/fileutils.h9
-rw-r--r--Lib/asyncio/unix_events.py11
-rw-r--r--Lib/asyncore.py6
-rw-r--r--Lib/test/test_asyncio/test_unix_events.py12
-rw-r--r--Lib/test/test_io.py18
-rw-r--r--Lib/test/test_os.py26
-rw-r--r--Lib/test/test_posix.py7
-rw-r--r--Lib/test/test_pty.py23
-rw-r--r--Lib/test/test_signal.py10
-rw-r--r--Misc/NEWS4
-rw-r--r--Modules/posixmodule.c54
-rw-r--r--Python/fileutils.c53
13 files changed, 198 insertions, 58 deletions
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index c03ce65..9cfc472 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -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
diff --git a/Include/fileutils.h b/Include/fileutils.h
index e9bad80..f2a43f7 100644
--- a/Include/fileutils.h
+++ b/Include/fileutils.h
@@ -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
}
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 @@ 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
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 @@ 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)
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 @@ 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()
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 @@ 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
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 @@ 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__":
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 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
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 @@ 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)
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 @@ 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)
diff --git a/Misc/NEWS b/Misc/NEWS
index 2db6da0..1dd0c47 100644
--- 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.
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index b7acbc3..0bee3c9 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -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]*/
@@ -11606,6 +11656,10 @@ static PyMethodDef posix_methods[] = {
{"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 */
};
diff --git a/Python/fileutils.c b/Python/fileutils.c
index a55064f..065d3fd 100644
--- a/Python/fileutils.c
+++ b/Python/fileutils.c
@@ -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
+