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 Fri Jun 24 17:04:44 2016 +0000 @@ -471,6 +471,19 @@ The returned socket is now non-inheritable. +.. function:: fromfd2(fd) + + Create a socket object from a file descriptor. The file descriptor is + not duplicated. The family, type and protocol for the socket are + determined from the file descriptor. If the file descriptor is not a + socket, :exc:`OSError` is raised. 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 +840,22 @@ .. 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. + + 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 Fri Jun 24 17:04:44 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 from an open 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,8 +62,8 @@ EAGAIN = getattr(errno, 'EAGAIN', 11) EWOULDBLOCK = getattr(errno, 'EWOULDBLOCK', 11) -__all__ = ["fromfd", "getfqdn", "create_connection", - "AddressFamily", "SocketKind"] +__all__ = ["fromfd", "fromfd2", "getfqdn", "create_connection", + "AddressFamily", "SocketKind", "fdtype"] __all__.extend(os._get_exports_list(_socket)) # Set up the socket.AF_* socket.SOCK_* constants as members of IntEnums for @@ -450,6 +452,29 @@ nfd = dup(fd) return socket(family, type, proto, nfd) +if hasattr(_socket, "fdtype"): + 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 Fri Jun 24 17:04:44 2016 +0000 @@ -4954,6 +4954,40 @@ socket.setdefaulttimeout(t) +@unittest.skipUnless(hasattr(socket, 'fdtype'), 'test needs socket.fdtype') +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 TYPES: + s = socket.socket(family, kind) + with s: + self.assertEqual(socket.fdtype(s.fileno()), + (family, kind, 0)) + + def test_fromfd2(self): + for family, kind in 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): diff -r f859380d4708 Modules/socketmodule.c --- a/Modules/socketmodule.c Fri Jun 24 12:57:18 2016 +0300 +++ b/Modules/socketmodule.c Fri Jun 24 17:04:44 2016 +0000 @@ -5148,6 +5148,57 @@ #endif /* HAVE_SOCKETPAIR */ +/* socket.fdtype() function */ + +#if defined(SO_TYPE) +#define HAVE_FDTYPE /* have enough to implement fdtype() */ +#endif + +#ifdef HAVE_FDTYPE +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.\ +"); +#endif /* HAVE_FDTYPE */ + + static PyObject * socket_ntohs(PyObject *self, PyObject *args) { @@ -6034,6 +6085,10 @@ {"socketpair", socket_socketpair, METH_VARARGS, socketpair_doc}, #endif +#ifdef HAVE_FDTYPE + {"fdtype", socket_fdtype, + METH_O, fdtype_doc}, +#endif {"ntohs", socket_ntohs, METH_VARARGS, ntohs_doc}, {"ntohl", socket_ntohl, @@ -6591,6 +6646,21 @@ #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_PASSCRED + PyModule_AddIntMacro(m, SO_PASSCRED); +#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