diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index d662457e0f4..cdfedc4673b 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -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 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 `_ + for a more thorough explanation. + + Availability: Linux >= 2.6.27. + + .. versionadded:: 3.2 .. data:: SO_* SOMAXCONN diff --git a/Lib/test/test_asyncore.py b/Lib/test/test_asyncore.py index 205efb96420..f2ec8b86c80 100644 --- a/Lib/test/test_asyncore.py +++ b/Lib/test/test_asyncore.py @@ -692,7 +692,8 @@ class BaseTestAPI(unittest.TestCase): s = asyncore.dispatcher() s.create_socket(socket.AF_INET, socket.SOCK_STREAM) 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): s1 = asyncore.dispatcher() diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 959e72a9e40..aadfdfaf206 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -17,6 +17,10 @@ import contextlib from weakref import proxy import signal import math +try: + import fcntl +except ImportError: + fcntl = False def try_address(host, port=0, family=socket.AF_INET): """Try to bind a socket on the given host:port and return True @@ -902,6 +906,26 @@ class NonBlockingTCPTests(ThreadedTCPSocketTest): def _testSetBlocking(self): 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): # Testing non-blocking accept self.serv.setblocking(0) @@ -1801,6 +1825,56 @@ class ContextManagersTest(ThreadedTCPSocketTest): self.assertTrue(sock._closed) 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(): tests = [GeneralModuleTests, BasicTCPTest, TCPCloserTest, TCPTimeoutTest, @@ -1820,6 +1894,8 @@ def test_main(): NetworkConnectionAttributesTest, NetworkConnectionBehaviourTest, ContextManagersTest, + CloexecConstantTest, + NonblockConstantTest ]) if hasattr(socket, "socketpair"): tests.append(BasicSocketPairTest) diff --git a/Misc/ACKS b/Misc/ACKS index 36472ed1e3d..f5c672540db 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -846,6 +846,7 @@ Alexandre Vassalotti Frank Vercruesse Mike Verdone Jaap Vermeulen +Nikita Vetoshkin Al Vezza Jacques A. Vidrine John Viega diff --git a/Misc/NEWS b/Misc/NEWS index 2d784122268..2089ceb24c2 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -21,12 +21,14 @@ Core and Builtins 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 not correct, change is made to raise a URLError when a remote host is tried to access via file:// scheme. - - 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 BadZipfile (rather than IOError) when opening small non-ZIP files. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 94d5f7c42be..50ca9114483 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -615,6 +615,12 @@ internal_setblocking(PySocketSockObject *s, int block) #ifndef MS_WINDOWS int delay_flag; #endif +#ifdef SOCK_NONBLOCK + if (block) + s->sock_type &= (~SOCK_NONBLOCK); + else + s->sock_type |= SOCK_NONBLOCK; +#endif Py_BEGIN_ALLOW_THREADS #ifndef MS_WINDOWS @@ -764,12 +770,18 @@ init_sockobject(PySocketSockObject *s, s->sock_family = family; s->sock_type = type; s->sock_proto = proto; - s->sock_timeout = defaulttimeout; s->errorhandler = &set_error; - - if (defaulttimeout >= 0.0) - internal_setblocking(s, 0); +#ifdef SOCK_NONBLOCK + if (type & SOCK_NONBLOCK) + 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) PyObject *addr = NULL; PyObject *res = NULL; int timeout; - +#ifdef HAVE_ACCEPT4 + int flags = 0; +#endif if (!getsockaddrlen(s, &addrlen)) return NULL; memset(&addrbuf, 0, addrlen); @@ -1656,8 +1670,15 @@ sock_accept(PySocketSockObject *s) BEGIN_SELECT_LOOP(s) Py_BEGIN_ALLOW_THREADS 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); +#endif /* HAVE_ACCEPT4 */ + } Py_END_ALLOW_THREADS if (timeout == 1) { @@ -4599,6 +4620,12 @@ PyInit__socket(void) #if defined(SOCK_RDM) PyModule_AddIntConstant(m, "SOCK_RDM", SOCK_RDM); #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 PyModule_AddIntConstant(m, "SO_DEBUG", SO_DEBUG); diff --git a/configure b/configure index 9299296820b..4a5890a8789 100755 --- a/configure +++ b/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.in Revision: 85349 . +# From configure.in Revision: 85422 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.65 for python 3.2. # @@ -9302,7 +9302,7 @@ fi $as_echo "MACHDEP_OBJS" >&6; } # 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 \ gai_strerror getgroups getlogin getloadavg getpeername getpgid getpid \ getpriority getresuid getresgid getpwent getspnam getspent getsid getwd \ diff --git a/configure.in b/configure.in index 31ed91eeb36..dc74743c14f 100644 --- a/configure.in +++ b/configure.in @@ -2550,7 +2550,7 @@ fi AC_MSG_RESULT(MACHDEP_OBJS) # 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 \ gai_strerror getgroups getlogin getloadavg getpeername getpgid getpid \ getpriority getresuid getresgid getpwent getspnam getspent getsid getwd \ diff --git a/pyconfig.h.in b/pyconfig.h.in index 545dbe38115..99ae85b5791 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -40,6 +40,9 @@ the case on Motorola V4 (R40V4.2) */ #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. */ #undef HAVE_ACOSH