diff -r f859380d4708 Doc/library/socket.rst --- a/Doc/library/socket.rst Fri Jun 24 12:57:18 2016 +0300 +++ b/Doc/library/socket.rst Tue Jun 28 20:57:52 2016 +0000 @@ -471,6 +471,22 @@ The returned socket is now non-inheritable. +.. function:: fromfd2(fd) + + Create a socket object from the file descriptor *fd* (as returned + by a file object's :meth:`fileno` method). The file descriptor + is not duplicated. If the file descriptor is not a socket, + :exc:`OSError` is raised. The family, type of the socket is + determined from the file descriptor. If possible, the protocol + is also determined. On some platforms determining the protocol is + not possible and :attr:`socket.proto` is set to zero. The socket + is assumed to be in blocking mode. + + Availability: Unix + + .. versionadded:: 3.6 + + .. function:: fromshare(data) Instantiate a socket from data obtained from the :meth:`socket.share` @@ -827,6 +843,24 @@ .. versionadded:: 3.3 +.. function:: fdtype(fd) + + Get socket information from a file descriptor. The return value + is a 3-tuple with the following structure: + + ``(family, type, proto)`` + + The values of *family*, *type*, *proto* are all integers and are + meant to be passed to the :func:`.socket` function. If the file + descriptor is not a socket, :exc:`OSError` is raised. On some + platforms determining the protocol is not possible and in those + cases it is returned as zero. + + Availability: Unix + + .. versionadded:: 3.6 + + .. _socket-objects: Socket Objects diff -r f859380d4708 Lib/socket.py --- a/Lib/socket.py Fri Jun 24 12:57:18 2016 +0300 +++ b/Lib/socket.py Tue Jun 28 20:57:52 2016 +0000 @@ -12,6 +12,7 @@ socket() -- create a new socket object socketpair() -- create a pair of new socket objects [*] fromfd() -- create a socket object from an open file descriptor [*] +fromfd2() -- create a socket object without duplicating a file descriptor [*] fromshare() -- create a socket object from data received from socket.share() [*] gethostname() -- return the current hostname gethostbyname() -- map a hostname to its IP number @@ -24,6 +25,7 @@ inet_ntoa() -- convert 32-bit packed format IP to string (123.45.67.89) socket.getdefaulttimeout() -- get the default timeout value socket.setdefaulttimeout() -- set the default timeout value +fdtype() -- get (family, type, protocol) from a socket file descriptor [*] create_connection() -- connects to an address, with an optional timeout and optional source address. @@ -60,7 +62,7 @@ EAGAIN = getattr(errno, 'EAGAIN', 11) EWOULDBLOCK = getattr(errno, 'EWOULDBLOCK', 11) -__all__ = ["fromfd", "getfqdn", "create_connection", +__all__ = ["fromfd", "fdtype", "fromfd2", "getfqdn", "create_connection", "AddressFamily", "SocketKind"] __all__.extend(os._get_exports_list(_socket)) @@ -450,6 +452,28 @@ nfd = dup(fd) return socket(family, type, proto, nfd) +def fdtype(fd): + """fdtype(fd) -> (family, type, proto) + + Return (family, type, proto) for a socket given a file descriptor. + Raises OSError if the file descriptor is not a socket. + """ + family, type, proto = _socket.fdtype(fd) + return (_intenum_converter(family, AddressFamily), + _intenum_converter(type, SocketKind), + proto) + +def fromfd2(fd): + """fromfd2(fd) -> socket object + + Create a socket object from the given file descriptor. Unlike fromfd, + the descriptor is not duplicated. The family, type and protocol of + the socket is determined using fdtype(). Raises OSError if the file + descriptor is not a socket. + """ + family, type, proto = _socket.fdtype(fd) + return socket(family, type, proto, fd) + if hasattr(_socket.socket, "share"): def fromshare(info): """ fromshare(info) -> socket object diff -r f859380d4708 Lib/test/test_socket.py --- a/Lib/test/test_socket.py Fri Jun 24 12:57:18 2016 +0300 +++ b/Lib/test/test_socket.py Tue Jun 28 20:57:52 2016 +0000 @@ -4954,6 +4954,43 @@ socket.setdefaulttimeout(t) +class FdTypeTests(unittest.TestCase): + TYPES = [ + (socket.AF_INET, socket.SOCK_STREAM), + (socket.AF_INET, socket.SOCK_DGRAM), + ] + if hasattr(socket, 'AF_UNIX'): + TYPES.extend([ + (socket.AF_UNIX, socket.SOCK_STREAM), + (socket.AF_UNIX, socket.SOCK_DGRAM), + ]) + + def test_fdtype(self): + for family, kind in self.TYPES: + s = socket.socket(family, kind) + with s: + family2, kind2, proto = socket.fdtype(s.fileno()) + self.assertEqual(family, family2) + self.assertEqual(kind, kind2) + # depending on platform, we may not be able to find proto, + # the returned value could be zero or the actual protocol + # used for the socket + + def test_fromfd2(self): + for family, kind in self.TYPES: + s = socket.socket(family, kind) + with s: + s2 = socket.fromfd2(s.fileno()) + try: + self.assertEqual(s.family, s2.family) + self.assertEqual(s.type, s2.type) + self.assertEqual(s.fileno(), s2.fileno()) + finally: + s2.detach() + with tempfile.TemporaryFile() as tmp: + self.assertRaises(OSError, socket.fromfd2, tmp.fileno()) + self.assertRaises(OSError, socket.fromfd2, -1) + @unittest.skipUnless(os.name == "nt", "Windows specific") @unittest.skipUnless(multiprocessing, "need multiprocessing") class TestSocketSharing(SocketTCPTest): @@ -5344,7 +5381,8 @@ NetworkConnectionBehaviourTest, ContextManagersTest, InheritanceTest, - NonblockConstantTest + NonblockConstantTest, + FdTypeTests, ]) tests.append(BasicSocketPairTest) tests.append(TestUnixDomain) diff -r f859380d4708 Modules/socketmodule.c --- a/Modules/socketmodule.c Fri Jun 24 12:57:18 2016 +0300 +++ b/Modules/socketmodule.c Tue Jun 28 20:57:52 2016 +0000 @@ -5148,6 +5148,50 @@ #endif /* HAVE_SOCKETPAIR */ +/* socket.fdtype() function */ +static PyObject * +socket_fdtype(PyObject *self, PyObject *fdobj) +{ + SOCKET_T fd; + int sock_type; + struct sockaddr sa; + socklen_t l; + int protocol; + + fd = PyLong_AsSocket_t(fdobj); + if (fd == (SOCKET_T)(-1) && PyErr_Occurred()) + return NULL; + + l = sizeof(sock_type); + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &l) < 0) { + return set_error(); + } + + l = sizeof(sa); + if (getsockname(fd, &sa, &l) < 0) { + return set_error(); + } +#ifdef SO_PROTOCOL + l = sizeof(protocol); + if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &protocol, &l) < 0) { + return set_error(); + } +#else + protocol = 0; +#endif + return Py_BuildValue("iii", + sa.sa_family, + sock_type, + protocol); +} + +PyDoc_STRVAR(fdtype_doc, +"fdtype(integer) -> (family, type, protocol)\n\ +\n\ +Return the family, type and protocol for socket given a file descriptor.\ +"); + + static PyObject * socket_ntohs(PyObject *self, PyObject *args) { @@ -6034,6 +6078,8 @@ {"socketpair", socket_socketpair, METH_VARARGS, socketpair_doc}, #endif + {"fdtype", socket_fdtype, + METH_O, fdtype_doc}, {"ntohs", socket_ntohs, METH_VARARGS, ntohs_doc}, {"ntohl", socket_ntohl, @@ -6591,6 +6637,18 @@ #ifdef SO_MARK PyModule_AddIntMacro(m, SO_MARK); #endif +#ifdef SO_DOMAIN + PyModule_AddIntMacro(m, SO_DOMAIN); +#endif +#ifdef SO_PROTOCOL + PyModule_AddIntMacro(m, SO_PROTOCOL); +#endif +#ifdef SO_PEERSEC + PyModule_AddIntMacro(m, SO_PEERSEC); +#endif +#ifdef SO_PASSSEC + PyModule_AddIntMacro(m, SO_PASSSEC); +#endif /* Maximum number of connections for "listen" */ #ifdef SOMAXCONN