diff Modules/socketmodule.c @ 85420:ef889c3d5dc6

Issue #18571: Implementation of the PEP 446: file descriptors and file handles are now created non-inheritable; add functions os.get/set_inheritable(), os.get/set_handle_inheritable() and socket.socket.get/set_inheritable().
author Victor Stinner <victor.stinner@gmail.com>
date Wed, 28 Aug 2013 00:53:59 +0200
parents 0872257752c2
children d37e1d846fe3
line wrap: on
line diff
--- a/Modules/socketmodule.c	Tue Aug 27 20:17:03 2013 +0300
+++ b/Modules/socketmodule.c	Wed Aug 28 00:53:59 2013 +0200
@@ -97,13 +97,14 @@
 
 /* Socket object documentation */
 PyDoc_STRVAR(sock_doc,
-"socket([family[, type[, proto]]]) -> socket object\n\
+"socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) -> socket object\n\
 \n\
 Open a socket of the given type.  The family argument specifies the\n\
 address family; it defaults to AF_INET.  The type argument specifies\n\
 whether this is a stream (SOCK_STREAM, this is the default)\n\
 or datagram (SOCK_DGRAM) socket.  The protocol argument defaults to 0,\n\
 specifying the default protocol.  Keyword arguments are accepted.\n\
+The socket is created as non-inheritable.\n\
 \n\
 A socket object represents one endpoint of a network connection.\n\
 \n\
@@ -114,7 +115,7 @@
 close() -- close the socket\n\
 connect(addr) -- connect the socket to a remote address\n\
 connect_ex(addr) -- connect, return an error code instead of an exception\n\
-_dup() -- return a new socket fd duplicated from fileno()\n\
+dup() -- return a new socket fd duplicated from fileno()\n\
 fileno() -- return underlying file descriptor\n\
 getpeername() -- return remote address [*]\n\
 getsockname() -- return local address\n\
@@ -356,22 +357,7 @@
 #endif
 
 #ifdef MS_WINDOWS
-/* On Windows a socket is really a handle not an fd */
-static SOCKET
-dup_socket(SOCKET handle)
-{
-    WSAPROTOCOL_INFO info;
-
-    if (WSADuplicateSocket(handle, GetCurrentProcessId(), &info))
-        return INVALID_SOCKET;
-
-    return WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,
-                     FROM_PROTOCOL_INFO, &info, 0, WSA_FLAG_OVERLAPPED);
-}
 #define SOCKETCLOSE closesocket
-#else
-/* On Unix we can use dup to duplicate the file descriptor of a socket*/
-#define dup_socket(fd) dup(fd)
 #endif
 
 #ifdef MS_WIN32
@@ -499,6 +485,11 @@
     (errno == expected)
 #endif
 
+#ifdef MS_WINDOWS
+/* Does WSASocket() support the WSA_FLAG_NO_HANDLE_INHERIT flag? */
+static int support_wsa_no_inherit = -1;
+#endif
+
 /* Convenience function to raise an error according to errno
    and return a NULL pointer from a function. */
 
@@ -1955,6 +1946,11 @@
     PyObject *addr = NULL;
     PyObject *res = NULL;
     int timeout;
+#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC)
+    /* accept4() is available on Linux 2.6.28+ and glibc 2.10 */
+    static int accept4_works = -1;
+#endif
+
     if (!getsockaddrlen(s, &addrlen))
         return NULL;
     memset(&addrbuf, 0, addrlen);
@@ -1963,10 +1959,24 @@
         return select_error();
 
     BEGIN_SELECT_LOOP(s)
+
     Py_BEGIN_ALLOW_THREADS
     timeout = internal_select_ex(s, 0, interval);
     if (!timeout) {
+#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC)
+        if (accept4_works != 0) {
+            newfd = accept4(s->sock_fd, SAS2SA(&addrbuf), &addrlen,
+                            SOCK_CLOEXEC);
+            if (newfd == INVALID_SOCKET && accept4_works == -1) {
+                /* On Linux older than 2.6.28, accept4() fails with ENOSYS */
+                accept4_works = (errno != ENOSYS);
+            }
+        }
+        if (accept4_works == 0)
+            newfd = accept(s->sock_fd, SAS2SA(&addrbuf), &addrlen);
+#else
         newfd = accept(s->sock_fd, SAS2SA(&addrbuf), &addrlen);
+#endif
     }
     Py_END_ALLOW_THREADS
 
@@ -1979,6 +1989,25 @@
     if (newfd == INVALID_SOCKET)
         return s->errorhandler();
 
+#ifdef MS_WINDOWS
+    if (!SetHandleInformation((HANDLE)newfd, HANDLE_FLAG_INHERIT, 0)) {
+        PyErr_SetFromWindowsErr(0);
+        SOCKETCLOSE(newfd);
+        goto finally;
+    }
+#else
+
+#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC)
+    if (!accept4_works)
+#endif
+    {
+        if (_Py_set_inheritable(newfd, 0, NULL) < 0) {
+            SOCKETCLOSE(newfd);
+            goto finally;
+        }
+    }
+#endif
+
     sock = PyLong_FromSocket_t(newfd);
     if (sock == NULL) {
         SOCKETCLOSE(newfd);
@@ -3909,6 +3938,12 @@
 
 /* Initialize a new socket object. */
 
+#ifdef SOCK_CLOEXEC
+/* socket() and socketpair() fail with EINVAL on Linux kernel older
+ * than 2.6.27 if SOCK_CLOEXEC flag is set in the socket type. */
+static int sock_cloexec_works = -1;
+#endif
+
 /*ARGSUSED*/
 static int
 sock_initobj(PyObject *self, PyObject *args, PyObject *kwds)
@@ -3918,6 +3953,13 @@
     SOCKET_T fd = INVALID_SOCKET;
     int family = AF_INET, type = SOCK_STREAM, proto = 0;
     static char *keywords[] = {"family", "type", "proto", "fileno", 0};
+#ifndef MS_WINDOWS
+#ifdef SOCK_CLOEXEC
+    int *atomic_flag_works = &sock_cloexec_works;
+#else
+    int *atomic_flag_works = NULL;
+#endif
+#endif
 
     if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                      "|iiiO:socket", keywords,
@@ -3962,14 +4004,74 @@
         }
     }
     else {
+#ifdef MS_WINDOWS
+        /* Windows implementation */
+#ifndef WSA_FLAG_NO_HANDLE_INHERIT
+#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
+#endif
+
         Py_BEGIN_ALLOW_THREADS
-        fd = socket(family, type, proto);
+        if (support_wsa_no_inherit) {
+            fd = WSASocket(family, type, proto,
+                           NULL, 0,
+                           WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT);
+            if (fd == INVALID_SOCKET) {
+                /* Windows 7 or Windows 2008 R2 without SP1 or the hotfix */
+                support_wsa_no_inherit = 0;
+                fd = socket(family, type, proto);
+            }
+        }
+        else {
+            fd = socket(family, type, proto);
+        }
         Py_END_ALLOW_THREADS
 
         if (fd == INVALID_SOCKET) {
             set_error();
             return -1;
         }
+
+        if (!support_wsa_no_inherit) {
+            if (!SetHandleInformation((HANDLE)fd, HANDLE_FLAG_INHERIT, 0)) {
+                closesocket(fd);
+                PyErr_SetFromWindowsErr(0);
+                return -1;
+            }
+        }
+#else
+        /* UNIX */
+        Py_BEGIN_ALLOW_THREADS
+#ifdef SOCK_CLOEXEC
+        if (sock_cloexec_works != 0) {
+            fd = socket(family, type | SOCK_CLOEXEC, proto);
+            if (sock_cloexec_works == -1) {
+                if (fd >= 0) {
+                    sock_cloexec_works = 1;
+                }
+                else if (errno == EINVAL) {
+                    /* Linux older than 2.6.27 does not support SOCK_CLOEXEC */
+                    sock_cloexec_works = 0;
+                    fd = socket(family, type, proto);
+                }
+            }
+        }
+        else
+#endif
+        {
+            fd = socket(family, type, proto);
+        }
+        Py_END_ALLOW_THREADS
+
+        if (fd == INVALID_SOCKET) {
+            set_error();
+            return -1;
+        }
+
+        if (_Py_set_inheritable(fd, 0, atomic_flag_works) < 0) {
+            SOCKETCLOSE(fd);
+            return -1;
+        }
+#endif
     }
     init_sockobject(s, fd, family, type, proto);
 
@@ -4535,16 +4637,36 @@
 {
     SOCKET_T fd, newfd;
     PyObject *newfdobj;
-
+#ifdef MS_WINDOWS
+    WSAPROTOCOL_INFO info;
+#endif
 
     fd = PyLong_AsSocket_t(fdobj);
     if (fd == (SOCKET_T)(-1) && PyErr_Occurred())
         return NULL;
 
-    newfd = dup_socket(fd);
+#ifdef MS_WINDOWS
+    if (WSADuplicateSocket(fd, GetCurrentProcessId(), &info))
+        return set_error();
+
+    newfd = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,
+                      FROM_PROTOCOL_INFO,
+                      &info, 0, WSA_FLAG_OVERLAPPED);
     if (newfd == INVALID_SOCKET)
         return set_error();
 
+    if (!SetHandleInformation((HANDLE)newfd, HANDLE_FLAG_INHERIT, 0)) {
+        closesocket(newfd);
+        PyErr_SetFromWindowsErr(0);
+        return NULL;
+    }
+#else
+    /* On UNIX, dup can be used to duplicate the file descriptor of a socket */
+    newfd = _Py_dup(fd);
+    if (newfd == INVALID_SOCKET)
+        return NULL; 
+#endif
+
     newfdobj = PyLong_FromSocket_t(newfd);
     if (newfdobj == NULL)
         SOCKETCLOSE(newfd);
@@ -4572,6 +4694,12 @@
     SOCKET_T sv[2];
     int family, type = SOCK_STREAM, proto = 0;
     PyObject *res = NULL;
+#ifdef SOCK_CLOEXEC
+    int *atomic_flag_works = &sock_cloexec_works;
+#else
+    int *atomic_flag_works = NULL;
+#endif
+    int ret;
 
 #if defined(AF_UNIX)
     family = AF_UNIX;
@@ -4581,9 +4709,38 @@
     if (!PyArg_ParseTuple(args, "|iii:socketpair",
                           &family, &type, &proto))
         return NULL;
+
     /* Create a pair of socket fds */
-    if (socketpair(family, type, proto, sv) < 0)
+    Py_BEGIN_ALLOW_THREADS
+#ifdef SOCK_CLOEXEC
+    if (sock_cloexec_works != 0) {
+        ret = socketpair(family, type | SOCK_CLOEXEC, proto, sv);
+        if (sock_cloexec_works == -1) {
+            if (ret >= 0) {
+                sock_cloexec_works = 1;
+            }
+            else if (errno == EINVAL) {
+                /* Linux older than 2.6.27 does not support SOCK_CLOEXEC */
+                sock_cloexec_works = 0;
+                ret = socketpair(family, type, proto, sv);
+            }
+        }
+    }
+    else
+#endif
+    {
+        ret = socketpair(family, type, proto, sv);
+    }
+    Py_END_ALLOW_THREADS
+
+    if (ret < 0)
         return set_error();
+
+    if (_Py_set_inheritable(sv[0], 0, atomic_flag_works) < 0)
+        goto finally;
+    if (_Py_set_inheritable(sv[1], 0, atomic_flag_works) < 0)
+        goto finally;
+
     s0 = new_sockobject(sv[0], family, type, proto);
     if (s0 == NULL)
         goto finally;
@@ -4605,7 +4762,7 @@
 }
 
 PyDoc_STRVAR(socketpair_doc,
-"socketpair([family[, type[, proto]]]) -> (socket object, socket object)\n\
+"socketpair([family[, type [, proto]]]) -> (socket object, socket object)\n\
 \n\
 Create a pair of socket objects from the sockets returned by the platform\n\
 socketpair() function.\n\
@@ -5539,6 +5696,16 @@
     if (!os_init())
         return NULL;
 
+#ifdef MS_WINDOWS
+    if (support_wsa_no_inherit == -1) {
+        DWORD version = GetVersion();
+        DWORD major = (DWORD)LOBYTE(LOWORD(version));
+        DWORD minor = (DWORD)HIBYTE(LOWORD(version));
+        /* need Windows 7 SP1, 2008 R2 SP1 or later */
+        support_wsa_no_inherit = (major >= 6 && minor >= 1);
+    }
+#endif
+
     Py_TYPE(&sock_type) = &PyType_Type;
     m = PyModule_Create(&socketmodule);
     if (m == NULL)