Hello

This is the rewritten-from-scratch implementation of the sendmsg()/recvmsg() methods. Any comments / suggestions / flames are very welcome. Currently it supports what I need and I'm only releasing it, because I don't have much time to develop it further in the forseeable future (1-2 months). It is rewritten from scratch, using the python c-api documents. I've tried my best, but I wouldn't bet that it works as it's supposed to. I'd be
glad if someone could give me a review on what I've done wrong.

The core parts are implemented correctly (I think), the features that are missing:
- using scatter/gather
- using it with non-stream oriented sockets (doesn't support addresses /msg_name/)

These should be very easy to implement though. I will fix the errors that are present right now, and if no one takes up the task I will implement the missing features also. You might
have to wait for it a little though.

Thanks in advance

Cheers,
Kalman Gergely
--- py3k_2/Modules/socketmodule.c	2009-07-23 17:07:55.474581000 +0200
+++ py3k/Modules/socketmodule.c	2009-07-23 17:22:16.880415500 +0200
@@ -2388,6 +2388,7 @@
 	return n;
 }
 
+
 /* s.recvfrom(nbytes [,flags]) method */
 
 static PyObject *
@@ -2440,6 +2441,143 @@
 Like recv(buffersize, flags) but also return the sender's address info.");
 
 
+/* s.recvmsg(datalen, controllen, flags) method */
+
+static PyObject *
+sock_recvmsg(PySocketSockObject *s, PyObject *args)
+{
+	PyObject *dbuf, *cbuf, *alist, *tmp;
+	ssize_t n = -1;
+	int status, timeout;
+	int rdlen, rclen, flags = 0;
+	struct msghdr mhdr;
+	struct cmsghdr *chdr;
+	struct iovec iov[1];
+
+	if (!PyArg_ParseTuple(args, "ii|i:recvmsg", &rdlen, &rclen, &flags))
+		return NULL;
+
+	if (rdlen < 0 || rclen < 0)
+	{
+		PyErr_SetString(PyExc_ValueError, "negative buffersize in recvmsg");
+		return NULL;
+	}
+
+	/* allocate buffers */
+	dbuf = PyBytes_FromStringAndSize((char *) 0, rdlen);
+	if (dbuf == NULL)
+	{
+		return NULL;
+	}
+
+	cbuf = PyBytes_FromStringAndSize((char *) 0, rclen);
+	if (cbuf == NULL)
+	{
+		Py_DECREF(dbuf);
+		return NULL;
+	}
+
+	alist = PyList_New(0);
+	if (alist == NULL)
+	{
+		Py_DECREF(dbuf);
+		Py_DECREF(cbuf);
+		return NULL;
+	}
+
+	/* set up the msghdr struct */
+	memset(&mhdr, 0, sizeof(struct msghdr));
+
+	// iov -- we use only one buffer, and don't use scatter-gather possible TODO
+	iov[0].iov_base = PyBytes_AS_STRING(dbuf);
+	iov[0].iov_len = rdlen;
+	memset(iov[0].iov_base, 0, iov[0].iov_len);
+
+	// msghdr
+	mhdr.msg_name = NULL;				// TODO make use of this
+	mhdr.msg_namelen = 0;
+	mhdr.msg_iov = iov;
+	mhdr.msg_iovlen = 1;
+	mhdr.msg_control = PyBytes_AS_STRING(cbuf);
+	mhdr.msg_controllen = rclen;
+	mhdr.msg_flags = 0;
+	memset(mhdr.msg_control, 0, mhdr.msg_controllen);
+
+	/* call recvmsg() */
+	Py_BEGIN_ALLOW_THREADS
+	timeout = internal_select(s, 0);
+	if (!timeout)
+		n = recvmsg(s->sock_fd, &mhdr, flags);
+	Py_END_ALLOW_THREADS
+
+	if (timeout == 1)
+	{
+		PyErr_SetString(socket_timeout, "timed out");
+		goto err;
+	}
+
+	if (n < 0)
+	{
+		s->errorhandler();
+		goto err;
+	}
+
+	/* process the ancillary data */
+	for (chdr = CMSG_FIRSTHDR(&mhdr); chdr != NULL; chdr = CMSG_NXTHDR(&mhdr, chdr))
+	{
+		tmp = Py_BuildValue("(iiy#)", chdr->cmsg_level, chdr->cmsg_type, (char *)CMSG_DATA(chdr),
+				// TODO XXX ugly hack, to compute CMSG_DATA's size
+				(int)chdr->cmsg_len - ((int)CMSG_DATA(chdr) - (int)chdr));
+		if (tmp == NULL)
+			goto err;
+
+		status = PyList_Append(alist, tmp);
+		Py_DECREF(tmp);
+
+		if (status == -1)
+			goto err;
+	}
+
+	/* if we received less than we anticipated, resize the buffer */
+	if (n != rdlen)
+	{
+		if (_PyBytes_Resize(&dbuf, n) == -1)
+		{
+			Py_DECREF(cbuf);
+			Py_DECREF(alist);
+			return NULL;
+		}
+	}
+
+	/* assemble the final return value, watch out for offsets! */
+	tmp = PyTuple_New(3);
+	if (tmp == NULL)
+		goto err;
+
+	PyTuple_SetItem(tmp, 0, dbuf);
+	PyTuple_SetItem(tmp, 1, alist);
+	PyTuple_SetItem(tmp, 2, Py_BuildValue("i", mhdr.msg_flags));
+
+	/* dbuf and alist are now in tmp, remove reference to cbuf and return */
+	Py_DECREF(cbuf);
+
+	return tmp;
+
+err:
+	Py_DECREF(dbuf);
+	Py_DECREF(cbuf);
+	Py_DECREF(alist);
+	return NULL;
+}
+
+PyDoc_STRVAR(recvmsg_doc,
+"recvmsg(datalen, controllen, flags) method -> (bytes(), [(msglevel, msgtype, msgdata) ... ], flags)\n\
+\n\
+Returns a tuple with three elements, 0: data bytes, 1: list of tuples with three elements\n\
+containing msg_level, msg_type, msg_data, 2: msg_flags\n\
+Currently it's incapable of using multiple buffers and addresses.");
+
+
 /* s.recvfrom_into(buffer[, nbytes [,flags]]) method */
 
 static PyObject *
@@ -2655,6 +2793,140 @@
 For IP sockets, the address is a pair (hostaddr, port).");
 
 
+/* s.sendmsg(data, [(msglevel, msgtype, msgdata), ...], flags) method */
+
+static PyObject *
+sock_sendmsg(PySocketSockObject *s, PyObject *args)
+{
+	Py_buffer dbuf;
+	PyObject *cbuf;
+	PyObject *tmp;
+	Py_buffer tmpdata;
+	PyObject *control;
+	size_t cbuflen = 0;
+	size_t controllen;
+	int tmplev, tmptype;
+	int timeout, flags = 0;
+	int i, n = -1;
+	struct msghdr mhdr;
+	struct cmsghdr *chdr;
+	struct iovec iov[1];
+
+	if (!PyArg_ParseTuple(args, "y*O!I|:sendmsg", &dbuf, &PyList_Type, &control, &flags)) {
+		return NULL;
+	}
+
+	controllen = PyList_Size(control);
+
+	/* set up the msghdr struct */
+	// count the bytes to allocate for msg_control
+	for (i=0; i<controllen; i++)
+	{
+		// get the list entry
+		tmp = PyList_GET_ITEM(control, i);
+		if (!PyTuple_CheckExact(tmp))
+		{
+			PyErr_SetString(PyExc_ValueError, "a list of tuples is needed");
+			return NULL;
+		}
+
+		// extract the fields
+		if (!PyArg_ParseTuple(tmp, "iiy*:sendmsg_inner", &tmplev, &tmptype, &tmpdata))
+		{
+			return NULL;
+		}
+
+		cbuflen += CMSG_SPACE(tmpdata.len);
+	}
+
+	// allocate a temporary buffer for msg_control
+	cbuf = PyBytes_FromStringAndSize((char *) 0, cbuflen);
+	if (cbuf == NULL)
+	{
+		return NULL;
+	}
+
+	// msghdr
+	mhdr.msg_name = NULL;				// TODO make use of this
+	mhdr.msg_namelen = 0;
+	mhdr.msg_iov = iov;
+	mhdr.msg_iovlen = 1;
+	mhdr.msg_control = PyBytes_AS_STRING(cbuf);
+	mhdr.msg_controllen = cbuflen;
+	mhdr.msg_flags = flags;
+
+	// iov -- For now we use only one buffer, and don't use scatter-gather. Possible TODO
+	iov[0].iov_base = dbuf.buf;
+	iov[0].iov_len = dbuf.len;
+
+	// cmsghdr
+	for (i=0, chdr = CMSG_FIRSTHDR(&mhdr); i<controllen && chdr != NULL; i++, chdr = CMSG_NXTHDR(&mhdr, chdr))
+	{
+		// get the list entry
+		tmp = PyList_GET_ITEM(control, i);
+		if (!PyTuple_CheckExact(tmp))
+		{
+			PyErr_SetString(PyExc_ValueError, "a list of tuples is needed");
+			goto err;
+		}
+
+		// extract the fields
+		if (!PyArg_ParseTuple(tmp, "iiy*:sendmsg_inner", &tmplev, &tmptype, &tmpdata))
+		{
+			goto err;
+		}
+
+		// set up the fields
+		chdr->cmsg_len = CMSG_LEN(tmpdata.len);
+		chdr->cmsg_level = tmplev;
+		chdr->cmsg_type = tmptype;
+		memcpy(CMSG_DATA(chdr), tmpdata.buf, tmpdata.len);
+	}
+
+	/* call sendmsg() */
+	if (!IS_SELECTABLE(s))
+	{
+		Py_DECREF(cbuf);
+		return select_error();
+	}
+
+
+	Py_BEGIN_ALLOW_THREADS
+	timeout = internal_select(s, 1);
+	if (!timeout)
+		n = sendmsg(s->sock_fd, &mhdr, flags);
+	Py_END_ALLOW_THREADS
+
+	// free the parameters
+	PyBuffer_Release(&dbuf);
+	Py_DECREF(cbuf);
+
+	if (timeout == 1)
+	{
+		PyErr_SetString(socket_timeout, "timed out");
+		return NULL;
+	}
+
+	if (n < 0)
+	{
+		return s->errorhandler();
+	}
+
+	return Py_BuildValue("I", n);
+
+err:
+	Py_DECREF(cbuf);
+	return NULL;
+}
+
+PyDoc_STRVAR(sendmsg_doc,
+"sendmsg(data, [(msglevel, msgtype, msgdata), ...], flags) -> sent\n\
+\n\
+Returns the bytes sent. Accepts 3 parameters, 1: the data bytes, 2: a list\n\
+containing tuples, that contain the msg_level, msg_type, msg_data, 3: flags.\n\
+Currently it's incapable of using multiple buffers and addresses.");
+
+
 /* s.shutdown(how) method */
 
 static PyObject *
@@ -2742,6 +3014,8 @@
 			  recv_into_doc},
 	{"recvfrom",	  (PyCFunction)sock_recvfrom, METH_VARARGS,
 			  recvfrom_doc},
+	{"recvmsg",	  (PyCFunction)sock_recvmsg, METH_VARARGS,
+			  recvmsg_doc},
 	{"recvfrom_into",  (PyCFunction)sock_recvfrom_into, METH_VARARGS | METH_KEYWORDS,
 			  recvfrom_into_doc},
 	{"send",	  (PyCFunction)sock_send, METH_VARARGS,
@@ -2750,6 +3024,8 @@
 			  sendall_doc},
 	{"sendto",	  (PyCFunction)sock_sendto, METH_VARARGS,
 			  sendto_doc},
+	{"sendmsg",	  (PyCFunction)sock_sendmsg, METH_VARARGS,
+			  sendmsg_doc},
 	{"setblocking",	  (PyCFunction)sock_setblocking, METH_O,
 			  setblocking_doc},
 	{"settimeout",    (PyCFunction)sock_settimeout, METH_O,
@@ -4513,6 +4789,14 @@
 	PyModule_AddIntConstant(m, "SO_TYPE", SO_TYPE);
 #endif
 
+	/* Ancilliary message types */
+#ifdef  SCM_RIGHTS
+	PyModule_AddIntConstant(m, "SCM_RIGHTS", SCM_RIGHTS);
+#endif
+#ifdef  SCM_CREDENTIALS
+	PyModule_AddIntConstant(m, "SCM_CREDENTIALS", SCM_CREDENTIALS);
+#endif
+
 	/* Maximum number of connections for "listen" */
 #ifdef	SOMAXCONN
 	PyModule_AddIntConstant(m, "SOMAXCONN", SOMAXCONN);
@@ -4551,6 +4835,9 @@
 #ifdef	MSG_ETAG
 	PyModule_AddIntConstant(m, "MSG_ETAG", MSG_ETAG);
 #endif
+#ifdef	MSG_ERRQUEUE
+	PyModule_AddIntConstant(m, "MSG_ERRQUEUE", MSG_ERRQUEUE);
+#endif
 
 	/* Protocol level and numbers, usable for [gs]etsockopt */
 #ifdef	SOL_SOCKET
import socket
import os
import sys
import struct
import time

s = socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET)

pid = os.fork()

if pid == -1:
	print("fork() failed!")
	sys.exit()

if pid == 0:
	msg = b'test'
	print("parent: hello")
	s[1].close()
	ipc = s[0]
	f = open("/etc/passwd", "r")
	tmp = struct.pack("i", f.fileno());
	print("parent: sending message", msg, tmp)
	ret = ipc.sendmsg(msg, [(socket.SOL_SOCKET, socket.SCM_RIGHTS, tmp)], 0)
	print("parent:", ret)
	f.close()
else:
	print("child: hello")
	s[0].close()
	ipc = s[1]
	ret = ipc.recvmsg(10, 1000, 0)
	print("child:", ret)
	print("child: received message:", ret[0])
	f = os.fdopen(struct.unpack("i", ret[1][0][2])[0])
	arr = f.readlines()
	for i in arr:
		print("Read:", i, end='')
	f.close()
_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to