Commit b1c54967 authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #7523: Add SOCK_CLOEXEC and SOCK_NONBLOCK to the socket module,

where supported by the system.  Patch by Nikita Vetoshkin.
parent f4061dac
...@@ -157,6 +157,21 @@ The module :mod:`socket` exports the following constants and functions: ...@@ -157,6 +157,21 @@ The module :mod:`socket` exports the following constants and functions:
:func:`socket`. (Only :const:`SOCK_STREAM` and :const:`SOCK_DGRAM` appear to be :func:`socket`. (Only :const:`SOCK_STREAM` and :const:`SOCK_DGRAM` appear to be
generally useful.) generally useful.)
.. data:: SOCK_CLOEXEC
SOCK_NONBLOCK
These two constants, if defined, can be combined with the socket types and
allow you to set some flags atomically (thus avoiding possible race
conditions and the need for separate calls).
.. seealso::
`Secure File Descriptor Handling <http://udrepper.livejournal.com/20407.html>`_
for a more thorough explanation.
Availability: Linux >= 2.6.27.
.. versionadded:: 3.2
.. data:: SO_* .. data:: SO_*
SOMAXCONN SOMAXCONN
......
...@@ -692,7 +692,8 @@ class BaseTestAPI(unittest.TestCase): ...@@ -692,7 +692,8 @@ class BaseTestAPI(unittest.TestCase):
s = asyncore.dispatcher() s = asyncore.dispatcher()
s.create_socket(socket.AF_INET, socket.SOCK_STREAM) s.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.assertEqual(s.socket.family, socket.AF_INET) self.assertEqual(s.socket.family, socket.AF_INET)
self.assertEqual(s.socket.type, socket.SOCK_STREAM) SOCK_NONBLOCK = getattr(socket, 'SOCK_NONBLOCK', 0)
self.assertEqual(s.socket.type, socket.SOCK_STREAM | SOCK_NONBLOCK)
def test_bind(self): def test_bind(self):
s1 = asyncore.dispatcher() s1 = asyncore.dispatcher()
......
...@@ -17,6 +17,10 @@ import contextlib ...@@ -17,6 +17,10 @@ import contextlib
from weakref import proxy from weakref import proxy
import signal import signal
import math import math
try:
import fcntl
except ImportError:
fcntl = False
def try_address(host, port=0, family=socket.AF_INET): def try_address(host, port=0, family=socket.AF_INET):
"""Try to bind a socket on the given host:port and return True """Try to bind a socket on the given host:port and return True
...@@ -902,6 +906,26 @@ class NonBlockingTCPTests(ThreadedTCPSocketTest): ...@@ -902,6 +906,26 @@ class NonBlockingTCPTests(ThreadedTCPSocketTest):
def _testSetBlocking(self): def _testSetBlocking(self):
pass pass
if hasattr(socket, "SOCK_NONBLOCK"):
def testInitNonBlocking(self):
# reinit server socket
self.serv.close()
self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM |
socket.SOCK_NONBLOCK)
self.port = support.bind_port(self.serv)
self.serv.listen(1)
# actual testing
start = time.time()
try:
self.serv.accept()
except socket.error:
pass
end = time.time()
self.assertTrue((end - start) < 1.0, "Error creating with non-blocking mode.")
def _testInitNonBlocking(self):
pass
def testAccept(self): def testAccept(self):
# Testing non-blocking accept # Testing non-blocking accept
self.serv.setblocking(0) self.serv.setblocking(0)
...@@ -1801,6 +1825,56 @@ class ContextManagersTest(ThreadedTCPSocketTest): ...@@ -1801,6 +1825,56 @@ class ContextManagersTest(ThreadedTCPSocketTest):
self.assertTrue(sock._closed) self.assertTrue(sock._closed)
self.assertRaises(socket.error, sock.sendall, b'foo') self.assertRaises(socket.error, sock.sendall, b'foo')
@unittest.skipUnless(hasattr(socket, "SOCK_CLOEXEC"),
"SOCK_CLOEXEC not defined")
@unittest.skipUnless(fcntl, "module fcntl not available")
class CloexecConstantTest(unittest.TestCase):
def test_SOCK_CLOEXEC(self):
s = socket.socket(socket.AF_INET,
socket.SOCK_STREAM | socket.SOCK_CLOEXEC)
self.assertTrue(s.type & socket.SOCK_CLOEXEC)
self.assertTrue(fcntl.fcntl(s, fcntl.F_GETFD) & fcntl.FD_CLOEXEC)
@unittest.skipUnless(hasattr(socket, "SOCK_NONBLOCK"),
"SOCK_NONBLOCK not defined")
class NonblockConstantTest(unittest.TestCase):
def checkNonblock(self, s, nonblock=True, timeout=0.0):
if nonblock:
self.assertTrue(s.type & socket.SOCK_NONBLOCK)
self.assertEqual(s.gettimeout(), timeout)
else:
self.assertFalse(s.type & socket.SOCK_NONBLOCK)
self.assertEqual(s.gettimeout(), None)
def test_SOCK_NONBLOCK(self):
# a lot of it seems silly and redundant, but I wanted to test that
# changing back and forth worked ok
s = socket.socket(socket.AF_INET,
socket.SOCK_STREAM | socket.SOCK_NONBLOCK)
self.checkNonblock(s)
s.setblocking(1)
self.checkNonblock(s, False)
s.setblocking(0)
self.checkNonblock(s)
s.settimeout(None)
self.checkNonblock(s, False)
s.settimeout(2.0)
self.checkNonblock(s, timeout=2.0)
s.setblocking(1)
self.checkNonblock(s, False)
# defaulttimeout
t = socket.getdefaulttimeout()
socket.setdefaulttimeout(0.0)
self.checkNonblock(socket.socket())
socket.setdefaulttimeout(None)
self.checkNonblock(socket.socket(), False)
socket.setdefaulttimeout(2.0)
self.checkNonblock(socket.socket(), timeout=2.0)
socket.setdefaulttimeout(None)
self.checkNonblock(socket.socket(), False)
socket.setdefaulttimeout(t)
def test_main(): def test_main():
tests = [GeneralModuleTests, BasicTCPTest, TCPCloserTest, TCPTimeoutTest, tests = [GeneralModuleTests, BasicTCPTest, TCPCloserTest, TCPTimeoutTest,
...@@ -1820,6 +1894,8 @@ def test_main(): ...@@ -1820,6 +1894,8 @@ def test_main():
NetworkConnectionAttributesTest, NetworkConnectionAttributesTest,
NetworkConnectionBehaviourTest, NetworkConnectionBehaviourTest,
ContextManagersTest, ContextManagersTest,
CloexecConstantTest,
NonblockConstantTest
]) ])
if hasattr(socket, "socketpair"): if hasattr(socket, "socketpair"):
tests.append(BasicSocketPairTest) tests.append(BasicSocketPairTest)
......
...@@ -846,6 +846,7 @@ Alexandre Vassalotti ...@@ -846,6 +846,7 @@ Alexandre Vassalotti
Frank Vercruesse Frank Vercruesse
Mike Verdone Mike Verdone
Jaap Vermeulen Jaap Vermeulen
Nikita Vetoshkin
Al Vezza Al Vezza
Jacques A. Vidrine Jacques A. Vidrine
John Viega John Viega
......
...@@ -21,12 +21,14 @@ Core and Builtins ...@@ -21,12 +21,14 @@ Core and Builtins
Library Library
------- -------
- Issue #Issue10063: file:// scheme will stop accessing remote hosts via ftp - Issue #7523: Add SOCK_CLOEXEC and SOCK_NONBLOCK to the socket module,
where supported by the system. Patch by Nikita Vetoshkin.
- Issue #10063: file:// scheme will stop accessing remote hosts via ftp
protocol. file:// urls had fallback to access remote hosts via ftp. This was protocol. file:// urls had fallback to access remote hosts via ftp. This was
not correct, change is made to raise a URLError when a remote host is tried not correct, change is made to raise a URLError when a remote host is tried
to access via file:// scheme. to access via file:// scheme.
- Issue #1710703: Write structures for an empty ZIP archive when a ZipFile is - Issue #1710703: Write structures for an empty ZIP archive when a ZipFile is
created in modes 'a' or 'w' and then closed without adding any files. Raise created in modes 'a' or 'w' and then closed without adding any files. Raise
BadZipfile (rather than IOError) when opening small non-ZIP files. BadZipfile (rather than IOError) when opening small non-ZIP files.
......
...@@ -615,6 +615,12 @@ internal_setblocking(PySocketSockObject *s, int block) ...@@ -615,6 +615,12 @@ internal_setblocking(PySocketSockObject *s, int block)
#ifndef MS_WINDOWS #ifndef MS_WINDOWS
int delay_flag; int delay_flag;
#endif #endif
#ifdef SOCK_NONBLOCK
if (block)
s->sock_type &= (~SOCK_NONBLOCK);
else
s->sock_type |= SOCK_NONBLOCK;
#endif
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
#ifndef MS_WINDOWS #ifndef MS_WINDOWS
...@@ -764,12 +770,18 @@ init_sockobject(PySocketSockObject *s, ...@@ -764,12 +770,18 @@ init_sockobject(PySocketSockObject *s,
s->sock_family = family; s->sock_family = family;
s->sock_type = type; s->sock_type = type;
s->sock_proto = proto; s->sock_proto = proto;
s->sock_timeout = defaulttimeout;
s->errorhandler = &set_error; s->errorhandler = &set_error;
#ifdef SOCK_NONBLOCK
if (defaulttimeout >= 0.0) if (type & SOCK_NONBLOCK)
internal_setblocking(s, 0); s->sock_timeout = 0.0;
else
#endif
{
s->sock_timeout = defaulttimeout;
if (defaulttimeout >= 0.0)
internal_setblocking(s, 0);
}
} }
...@@ -1645,7 +1657,9 @@ sock_accept(PySocketSockObject *s) ...@@ -1645,7 +1657,9 @@ sock_accept(PySocketSockObject *s)
PyObject *addr = NULL; PyObject *addr = NULL;
PyObject *res = NULL; PyObject *res = NULL;
int timeout; int timeout;
#ifdef HAVE_ACCEPT4
int flags = 0;
#endif
if (!getsockaddrlen(s, &addrlen)) if (!getsockaddrlen(s, &addrlen))
return NULL; return NULL;
memset(&addrbuf, 0, addrlen); memset(&addrbuf, 0, addrlen);
...@@ -1656,8 +1670,15 @@ sock_accept(PySocketSockObject *s) ...@@ -1656,8 +1670,15 @@ sock_accept(PySocketSockObject *s)
BEGIN_SELECT_LOOP(s) BEGIN_SELECT_LOOP(s)
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
timeout = internal_select_ex(s, 0, interval); timeout = internal_select_ex(s, 0, interval);
if (!timeout) if (!timeout) {
#ifdef HAVE_ACCEPT4
/* inherit socket flags and use accept4 call */
flags = s->sock_type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
newfd = accept4(s->sock_fd, SAS2SA(&addrbuf), &addrlen, flags);
#else
newfd = accept(s->sock_fd, SAS2SA(&addrbuf), &addrlen); newfd = accept(s->sock_fd, SAS2SA(&addrbuf), &addrlen);
#endif /* HAVE_ACCEPT4 */
}
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
if (timeout == 1) { if (timeout == 1) {
...@@ -4599,6 +4620,12 @@ PyInit__socket(void) ...@@ -4599,6 +4620,12 @@ PyInit__socket(void)
#if defined(SOCK_RDM) #if defined(SOCK_RDM)
PyModule_AddIntConstant(m, "SOCK_RDM", SOCK_RDM); PyModule_AddIntConstant(m, "SOCK_RDM", SOCK_RDM);
#endif #endif
#ifdef SOCK_CLOEXEC
PyModule_AddIntConstant(m, "SOCK_CLOEXEC", SOCK_CLOEXEC);
#endif
#ifdef SOCK_NONBLOCK
PyModule_AddIntConstant(m, "SOCK_NONBLOCK", SOCK_NONBLOCK);
#endif
#ifdef SO_DEBUG #ifdef SO_DEBUG
PyModule_AddIntConstant(m, "SO_DEBUG", SO_DEBUG); PyModule_AddIntConstant(m, "SO_DEBUG", SO_DEBUG);
......
#! /bin/sh #! /bin/sh
# From configure.in Revision: 85349 . # From configure.in Revision: 85422 .
# Guess values for system-dependent variables and create Makefiles. # Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.65 for python 3.2. # Generated by GNU Autoconf 2.65 for python 3.2.
# #
...@@ -9302,7 +9302,7 @@ fi ...@@ -9302,7 +9302,7 @@ fi
$as_echo "MACHDEP_OBJS" >&6; } $as_echo "MACHDEP_OBJS" >&6; }
# checks for library functions # checks for library functions
for ac_func in alarm setitimer getitimer bind_textdomain_codeset chown \ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
clock confstr ctermid execv fchmod fchown fork fpathconf ftime ftruncate \ clock confstr ctermid execv fchmod fchown fork fpathconf ftime ftruncate \
gai_strerror getgroups getlogin getloadavg getpeername getpgid getpid \ gai_strerror getgroups getlogin getloadavg getpeername getpgid getpid \
getpriority getresuid getresgid getpwent getspnam getspent getsid getwd \ getpriority getresuid getresgid getpwent getspnam getspent getsid getwd \
......
...@@ -2550,7 +2550,7 @@ fi ...@@ -2550,7 +2550,7 @@ fi
AC_MSG_RESULT(MACHDEP_OBJS) AC_MSG_RESULT(MACHDEP_OBJS)
# checks for library functions # checks for library functions
AC_CHECK_FUNCS(alarm setitimer getitimer bind_textdomain_codeset chown \ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
clock confstr ctermid execv fchmod fchown fork fpathconf ftime ftruncate \ clock confstr ctermid execv fchmod fchown fork fpathconf ftime ftruncate \
gai_strerror getgroups getlogin getloadavg getpeername getpgid getpid \ gai_strerror getgroups getlogin getloadavg getpeername getpgid getpid \
getpriority getresuid getresgid getpwent getspnam getspent getsid getwd \ getpriority getresuid getresgid getpwent getspnam getspent getsid getwd \
......
...@@ -40,6 +40,9 @@ ...@@ -40,6 +40,9 @@
the case on Motorola V4 (R40V4.2) */ the case on Motorola V4 (R40V4.2) */
#undef GETTIMEOFDAY_NO_TZ #undef GETTIMEOFDAY_NO_TZ
/* Define to 1 if you have the `accept4' function. */
#undef HAVE_ACCEPT4
/* Define to 1 if you have the `acosh' function. */ /* Define to 1 if you have the `acosh' function. */
#undef HAVE_ACOSH #undef HAVE_ACOSH
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment