1
/* fdsend - SCM_RIGHTS file descriptor passing for Python.
3
* $Id: fdsend.c,v 1.1.1.1 2004/11/04 06:15:03 mjp Exp $
5
* Copyright (C) 2004 Michael J. Pomraning <mjp{AT}pilcrow.madison.wi.us>
7
* This program is free software; you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation; either version 2 of the License, or
10
* (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with this program; if not, write to the Free Software
19
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
* Portions of this file -- preliminary defines and socketpair() routine --
22
* are derived from scgi version 1.2, which is is Copyright (c) 2004
23
* Corporation for National Research Initiatives; All Rights Reserved.
30
#define _XOPEN_SOURCE 500
32
#ifndef _XOPEN_SOURCE_EXTENDED
33
#define _XOPEN_SOURCE_EXTENDED 1 /* Solaris <= 2.7 needs this too */
35
#endif /* __OpenBSD__ */
37
#include <sys/types.h>
38
#include <sys/socket.h>
42
static PyObject *socketmodule_error = NULL;
46
* A PyArg_Parse... format converter.
49
obj2fd(PyObject *o, void *p) {
55
if ((fd = PyObject_AsFileDescriptor(o)) == -1)
65
* Cram a sequence of open files (or file descriptors) into the ancillary
66
* data of a msghdr. Note that the msg_control member is dynamically
67
* allocated by this function, and may be freed by a call to
68
* free_packed_control().
71
pack_control(PyObject *seq, struct msghdr *msg) {
72
PyObject *fast_seq = NULL;
78
fast_seq = PySequence_Fast(seq, "files argument must be a sequence");
79
if (NULL == fast_seq) return 0;
81
sz = PySequence_Fast_GET_SIZE(fast_seq);
83
msg->msg_controllen = 0;
84
msg->msg_control = NULL;
87
msg->msg_controllen = CMSG_SPACE(sizeof(int) * sz);
88
msg->msg_control = PyMem_Malloc(msg->msg_controllen);
89
if (NULL == msg->msg_control) return 0;
91
cmsg = CMSG_FIRSTHDR(msg);
92
cmsg->cmsg_len = CMSG_LEN(sizeof(int) * sz);
93
cmsg->cmsg_level = SOL_SOCKET;
94
cmsg->cmsg_type = SCM_RIGHTS;
95
fd_ptr = (int *)CMSG_DATA(cmsg);
96
for (i = 0; i < sz; i++) {
100
f = PySequence_Fast_GET_ITEM(fast_seq, i);
101
if (f == NULL) goto error;
102
if ((fd = PyObject_AsFileDescriptor(f)) == -1) goto error;
114
if (msg->msg_control) PyMem_Free(msg->msg_control);
119
free_packed_control(struct msghdr *msg)
121
if (NULL == msg) return;
122
if (msg->msg_control) PyMem_Free(msg->msg_control);
128
* Unpack the ancillary data, assuming SCM_RIGHTS (file descriptor passing).
131
* - more than one cmsghdr
132
* - more than one fd per cmsghdr
135
unpack_control(struct msghdr *msg) {
136
struct cmsghdr *cmsg;
138
PyObject *tup = NULL;
140
for (cmsg = CMSG_FIRSTHDR(msg);
142
cmsg = CMSG_NXTHDR(msg,cmsg)) {
143
if (cmsg->cmsg_level != SOL_SOCKET
144
|| cmsg->cmsg_type != SCM_RIGHTS) {
145
PyErr_SetString(PyExc_RuntimeError,
146
"Unexpected cmsg level or type found");
149
i += (cmsg->cmsg_len - sizeof(struct cmsghdr))/sizeof(int);
152
tup = PyTuple_New(i);
153
if (NULL == tup) goto error;
156
for (cmsg = CMSG_FIRSTHDR(msg);
158
cmsg = CMSG_NXTHDR(msg,cmsg)) {
160
int *pfd = (int *)CMSG_DATA(cmsg);
162
upper_bound = cmsg->cmsg_len
163
- sizeof(struct cmsghdr)
165
for (j = 0; j <= upper_bound; j += sizeof(int)) {
166
PyTuple_SET_ITEM(tup, i, PyLong_FromLong((long)pfd[i]));
172
if (tup) { Py_DECREF(tup); }
178
fdsend_sendfds(PyObject *dummy, PyObject *args, PyObject *kw)
182
int fd, r, flags = 0;
183
PyObject *fds = Py_None;
185
static char *keywords[] = {"fd", "msg", "flags", "fds", 0};
186
if (!PyArg_ParseTupleAndKeywords(args, kw, "O&s#|iO:sendfds",
189
&iov.iov_base, &iov.iov_len,
193
memset(&mh, '\0', sizeof(mh));
197
if (fds != Py_None) {
198
if (! pack_control(fds, &mh)) return NULL;
200
Py_BEGIN_ALLOW_THREADS
201
r = sendmsg(fd, &mh, flags);
203
free_packed_control(&mh);
206
return PyErr_SetFromErrno(socketmodule_error);
208
return PyInt_FromLong((long) r);
212
fdsend_recvfds(PyObject *dummy, PyObject *args, PyObject *kw)
216
struct cmsghdr *cmsg;
219
int fd, r, flags = 0;
220
PyObject *ret = NULL;
222
static char *keywords[] = {"fd", "len", "flags", "numfds", 0};
223
if (!PyArg_ParseTupleAndKeywords(args, kw, "O&i|ii:recvfds", keywords,
224
obj2fd, &fd, &iov.iov_len, &flags,
228
memset(&mh, '\0', sizeof(mh));
231
mh.msg_controllen = CMSG_SPACE(sizeof(int) * numfds);
232
mh.msg_control = PyMem_Malloc(mh.msg_controllen);
233
if (NULL == mh.msg_control) return NULL;
236
buf = PyString_FromStringAndSize((char *) 0, iov.iov_len);
237
if (NULL == buf) goto error;
238
iov.iov_base = (void *)PyString_AS_STRING(buf);
239
/* uncomment the following for clearer strace(1)ing */
240
/* memset(iov.iov_base, '\0', iov.iov_len); */
245
Py_BEGIN_ALLOW_THREADS
246
r = recvmsg(fd, &mh, flags);
250
PyErr_SetFromErrno(socketmodule_error);
254
if (r != iov.iov_len)
255
_PyString_Resize(&buf, r);
256
cmsg = CMSG_FIRSTHDR(&mh);
258
|| cmsg->cmsg_level != SOL_SOCKET
259
|| cmsg->cmsg_type != SCM_RIGHTS)
260
return Py_BuildValue("(N())", buf);
262
ret = Py_BuildValue("(OO)", buf, unpack_control(&mh));
265
if (mh.msg_control) PyMem_Free(mh.msg_control);
269
if (buf) { Py_DECREF(buf); }
273
static char fdsend_sendfds__doc__[] =
274
"sendfds(fd, msg, flags=0, fds=None) -> bytes_sent\n"
276
"Send msg across the socket represented by fd, optionally accompanied by a\n"
277
"sequence (tuple or list) of open file handles. For example:\n"
279
" >>> devnull = open(\"/dev/null\")\n"
280
" >>> sendfds(the_socket, \"null device\", (devnull,))\n"
282
"The socket fd and members of the fds sequence may be any representation\n"
283
"described in the module docstring.\n"
285
"Note that most underlying implementations require at least a one byte msg\n"
286
"to transmit open files.";
288
static char fdsend_recvfds__doc__[] =
289
"recvfds(fd, len, flags=0, numfds=64) -> (message, fd_tuple)\n"
291
"Receive a message of up to length len and up to numfds new files from socket\n"
294
"Though the socket object may be given as any of the representations listed\n"
295
"in the module docstring, new files returned in fd_tuple are always integral\n"
296
"file descriptors. See os.fdopen for a means of transforming them into\n"
297
"Python file objects.\n"
299
"There is presently no way to detect msg_flags values (e.g., MSG_CTRUNC).";
301
static char socketpair__doc__[] =
302
"socketpair(family, type, proto=0) -> (fd, fd)\n"
304
"Provided as a convenience for Python versions lacking a socket.socketpair\n"
308
fdsend_socketpair(PyObject *self, PyObject *args)
310
int family, type, proto=0;
313
if (!PyArg_ParseTuple(args, "ii|i:socketpair", &family, &type, &proto))
316
if (socketpair(family, type, proto, fd) < 0) {
317
PyErr_SetFromErrno(PyExc_IOError);
321
return Py_BuildValue("(ii)", (long) fd[0], (long) fd[1]);
325
/* List of functions */
327
static PyMethodDef fdsend_methods[] = {
328
{"sendfds", (PyCFunction)fdsend_sendfds,
329
METH_VARARGS|METH_KEYWORDS, fdsend_sendfds__doc__},
330
{"recvfds", (PyCFunction)fdsend_recvfds,
331
METH_VARARGS|METH_KEYWORDS, fdsend_recvfds__doc__},
332
{"socketpair", fdsend_socketpair, METH_VARARGS, socketpair__doc__},
333
{NULL, NULL} /* sentinel */
336
static char module__doc__[] =
337
"fdsend allows the passing of open files between unrelated processes via\n"
338
"local sockets (using SCM_RIGHTS), a process known as file descriptor\n"
339
"passing. The following functions are available:\n"
345
"Unlike some other simplifications of the sendmsg()/recvmsg() interface,\n"
346
"fdsend allows multiple files to be transferred in a single operation, and\n"
347
"permits ordinary socket messages to accompany the files. Additionally,\n"
348
"fdsend understands bona fide Python sockets and files, as well as objects\n"
349
"implementing fileno() methods and integers representing file descriptors.\n"
351
"Errors are raised via the socket.error exception object.";
358
/* Create the module and add the functions and documentation */
359
m = Py_InitModule3("fdsend", fdsend_methods, module__doc__);
360
if ((sm = PyImport_ImportModule("socket")) != NULL) {
361
socketmodule_error = PyObject_GetAttrString(sm, "error");