Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Passing file descriptors in Python?

732 views
Skip to first unread message

nasc...@enme.ucalgary.ca

unread,
Oct 1, 1999, 3:00:00 AM10/1/99
to pytho...@python.org
On Sat, Oct 02, 1999 at 02:12:28AM +0000, Dan wrote:
> Has anyone attempted to pass file descriptors from one process to
> another under using Python on Linux? I want to build a little web
> server that accepts connections and hands them off to child processes
> exactly like Apache does.

I don't know too much about how apache works but fork works just
fine with Python:

>>> import os
>>> f = open('foo', 'w')
>>> if os.fork() == 0:
... print 'child', f.fileno()
... else:
... print 'parent', f.fileno()
...
parent 3
child 3


Skip Montanaro

unread,
Oct 1, 1999, 3:00:00 AM10/1/99
to nasc...@enme.ucalgary.ca

Neil> On Sat, Oct 02, 1999 at 02:12:28AM +0000, Dan wrote:
>> Has anyone attempted to pass file descriptors from one process to
>> another under using Python on Linux? I want to build a little web
>> server that accepts connections and hands them off to child processes
>> exactly like Apache does.

Neil> I don't know too much about how apache works but fork works just
Neil> fine with Python:

Unfortunately, that's not what Apache does. Apache pre-forks a bunch of
children. The parent then (through some clever magic supported on a number
of Unix variants which I don't understand) passes the open file descriptor
to an already forked process. You can set a limit on the number of requests
a child will handle before exiting. Set it high enough and (practically
speaking) you won't notice any fork overhead.

Skip Montanaro | http://www.mojam.com/
sk...@mojam.com | http://www.musi-cal.com/
847-971-7098 | Python: Programming the way Guido indented...

Dan

unread,
Oct 2, 1999, 3:00:00 AM10/2/99
to

Has anyone attempted to pass file descriptors from one process to
another under using Python on Linux? I want to build a little web
server that accepts connections and hands them off to child processes
exactly like Apache does.

If anyone has an existing module that handles this or has sample code
they don't mind sharing or even some good tips to get me going in the
right direction it would be greatly appreciated. I've found examples
on DejaNews of C code for Linux but writing a C module for Python is a
little beyond me.

Thanks!
-Dan

Dan

unread,
Oct 2, 1999, 3:00:00 AM10/2/99
to

I suppose I should clarify my request. :-)

What I want to do is to have a parent process spawn off a child
process which then waits for new file descriptor (socket) to come from
the parent. The parent after it spawns several children will wait
listen on a socket for new connections which it will pass to one of
the child processes after the connection has been established.

My goal here is to make a Python based web server that will run on
more than one processor simultaniously. I've already built one that
uses threading but unfortunately Python has a global interpreter lock
which keeps two threads from running at the same time on a
multi-processor machine. So I need to go with a multi-process scheme
in order to take advantage of a multi-processor system but the problem
is that I'm having a difficult time figuring out how to pass a file
descriptor from one process ro another since the only sample code I
can find is written in C.

-Dan

On Fri, 1 Oct 1999 20:30:34 -0600, nasc...@enme.ucalgary.ca wrote:

>On Sat, Oct 02, 1999 at 02:12:28AM +0000, Dan wrote:

>> Has anyone attempted to pass file descriptors from one process to
>> another under using Python on Linux? I want to build a little web
>> server that accepts connections and hands them off to child processes
>> exactly like Apache does.
>

>I don't know too much about how apache works but fork works just

Tres Seaver

unread,
Oct 2, 1999, 3:00:00 AM10/2/99
to

In article <37f5aa20...@news.supernews.com>,

Dan <puter_pr...@yahoo.com> wrote:
>
>
>I suppose I should clarify my request. :-)
>
>What I want to do is to have a parent process spawn off a child
>process which then waits for new file descriptor (socket) to come from
>the parent. The parent after it spawns several children will wait
>listen on a socket for new connections which it will pass to one of
>the child processes after the connection has been established.
>
>My goal here is to make a Python based web server that will run on
>more than one processor simultaniously. I've already built one that
>uses threading but unfortunately Python has a global interpreter lock
>which keeps two threads from running at the same time on a
>multi-processor machine. So I need to go with a multi-process scheme
>in order to take advantage of a multi-processor system but the problem
>is that I'm having a difficult time figuring out how to pass a file
>descriptor from one process ro another since the only sample code I
>can find is written in C.

The bible for this kind of stuff is W. S. Stevens'
_Unix_Network_Programming_. Volume 1 of the second edition, section 27.9,
has exactly the kind of setup you describe. The mechanism used is quite
arcane: essentially, the two processes use a Unix-domain socket to pass
the descriptor via sendmsg() / recvmsg(); exact details depend on the
actual kernel used.

--
---------------------------------------------------------------
Tres Seaver tse...@palladion.com 713-523-6582
Palladion Software http://www.palladion.com

Viktor Fougstedt

unread,
Oct 2, 1999, 3:00:00 AM10/2/99
to
puter_pr...@yahoo.com (Dan) writes:

>I suppose I should clarify my request. :-)

>What I want to do is to have a parent process spawn off a child
>process which then waits for new file descriptor (socket) to come from
>the parent. The parent after it spawns several children will wait
>listen on a socket for new connections which it will pass to one of
>the child processes after the connection has been established.


Depending on the UNIX flavour you use, the passing of open
filedescriptors is done in different ways. On most System-V-based
systems, such as Solaris, sending an open fildescriptor in regular
C-programs requires you to have an open STREAM between the two
processes (a pipe will do fine most of the time). You then ioctl()
this stream with a I_SENDFD in the sender end and a I_RECVFD in the
receiver end, and your filedescriptor will be passed.

I do not know if you can use the fcntl.ioctl() call in Python to do
the same thing, but my guess is it should work (if you are on a
machine whose UNIX dialect supports the I_SENDFD/I_RECVFD ioctls, that
is. Check your local ioctl() manpage). A quick glance at the Python
interpreter's source-code seems to indicate that this _should_ work,
but I'm not certain.

I suppose the best thing would be to actually try it. :-) IPC in UNIX
isn't exactly a breeze to get working properly, and sending
open fildescriptors between otherwise unrelated processes is not the
easiest form of IPC there is, either.

If I get the time tonight, I'll give it a shot and see if I can make
it work.


/Viktor...

--
--| Viktor Fougstedt, system administrator at dtek.chalmers.se |--
--| http://www.dtek.chalmers.se/~viktor/ |--
--| ...soon we'll be sliding down the razor blade of life. /Tom Lehrer |--

Viktor Fougstedt

unread,
Oct 2, 1999, 3:00:00 AM10/2/99
to
vik...@dtek.chalmers.se (Viktor Fougstedt) writes:

>I do not know if you can use the fcntl.ioctl() call in Python to do
>the same thing, but my guess is it should work (if you are on a
>machine whose UNIX dialect supports the I_SENDFD/I_RECVFD ioctls, that
>is. Check your local ioctl() manpage). A quick glance at the Python
>interpreter's source-code seems to indicate that this _should_ work,
>but I'm not certain.

>If I get the time tonight, I'll give it a shot and see if I can make
>it work.


Following up on my own post, of course I couldn't stop myself from
trying at once. It seems to work, which impresses me of Pythons
transparency towards the underlying OS.

Example code below, seems to work fine on Solaris. Not sure which
other UNIX dialects support I_SENDFD/I_RECVFD ioctl()s, but it should
work on them as well.


/Viktor...

======================================================================
#!/usr/pd/bin/python
#

#
# Example of passing an open filedescriptor with Python. Will only work
# on UNIX dialects that support the I_SENDFD and I_RECVFD ioctl() calls.
#

# STROPTS was created using Tools/scripts/h2py from Python's sourcecode
# distribution.

import STROPTS, fcntl, os, sys, struct

#
# Create a pipe for sending the fd.
#

(pRead, pWrite) = os.pipe()


#
# fork() off!
#

pid = os.fork()

if pid != 0:
# We're in the parent.

# Open a file for passing along to child. Use own source code,
# which is guaranteed to exist. :)

fileObj = open('./fdpass.py', 'r')

# ioctl() will only pass raw filedescriptors. Find fd of fileObj.
fd = fileObj.fileno()

# Send to the child using ioctl().
retval = fcntl.ioctl(pWrite, STROPTS.I_SENDFD, fd)

# Should probably check retval rather than just printing it. :)
print "Parent ioctl() returned %d" % retval

# Wait for child to terminate, then exit.
os.waitpid(pid, 0)
sys.exit(0)

else:
# We're in the child.

# Create a string representing the strrecvfd structure that ioctl()
# will return.
str = struct.pack('iii', 0, 0, 0)

# Receive filedescriptor. Will block until descriptor is sent.
ret = fcntl.ioctl(pRead, STROPTS.I_RECVFD, str)

# Unpack the strrecvfd-structure that ioctl() should return.
# fd is the filedescriptor, uid/gid the user/group id of the
# sending stream.
(fd, uid, gid) = struct.unpack('iii', ret)

# Reopen the filedescriptor as a Python File-object.
fileObj = os.fdopen(fd, 'r')

# Example usage: Read file, print the first line.
lines = fileObj.readlines()
print lines[0],
sys.exit(0)


======================================================================

Dan

unread,
Oct 2, 1999, 3:00:00 AM10/2/99
to
On 2 Oct 1999 15:17:53 GMT, tse...@starbase.neosoft.com (Tres Seaver)
wrote:

>
>In article <37f5aa20...@news.supernews.com>,
>Dan <puter_pr...@yahoo.com> wrote:
>>
>>

>>I suppose I should clarify my request. :-)
>>
>>What I want to do is to have a parent process spawn off a child
>>process which then waits for new file descriptor (socket) to come from
>>the parent. The parent after it spawns several children will wait
>>listen on a socket for new connections which it will pass to one of
>>the child processes after the connection has been established.
>>

>>My goal here is to make a Python based web server that will run on
>>more than one processor simultaniously. I've already built one that
>>uses threading but unfortunately Python has a global interpreter lock
>>which keeps two threads from running at the same time on a
>>multi-processor machine. So I need to go with a multi-process scheme
>>in order to take advantage of a multi-processor system but the problem
>>is that I'm having a difficult time figuring out how to pass a file
>>descriptor from one process ro another since the only sample code I
>>can find is written in C.
>
>The bible for this kind of stuff is W. S. Stevens'
>_Unix_Network_Programming_. Volume 1 of the second edition, section 27.9,
>has exactly the kind of setup you describe. The mechanism used is quite
>arcane: essentially, the two processes use a Unix-domain socket to pass
>the descriptor via sendmsg() / recvmsg(); exact details depend on the
>actual kernel used.


Actually, I have Stevens' "Advanced Programming in the Unix
Environment" here (the other two are on their way) and it has a
chapter regarding the process too. I've read it but unfortunately I'm
just getting started in C. The most C I've done so far is to write a
hello world extension module for Python. ;-)

There is also a usenet posting that probably has a more specific
example of how to do it under Linux than any of Stevens' books.
http://x26.deja.com/getdoc.xp?AN=443099042

Judging from this I am assuming that it's not possible with pure
Python code since socketpair() is not available under Python. Is there
any paticular reason this function was not included in the Python
socket library?

From the sounds of it my only option is to turn my hello_world module
into a fdpass module myself. I don't suppose I'll be re-inventing the
wheel here will I? ;-)

-Dan

Dan

unread,
Oct 2, 1999, 3:00:00 AM10/2/99
to
On 2 Oct 1999 17:30:18 GMT, vik...@dtek.chalmers.se (Viktor Fougstedt)
wrote:

>puter_pr...@yahoo.com (Dan) writes:
>
>
>
>>I suppose I should clarify my request. :-)
>
>>What I want to do is to have a parent process spawn off a child
>>process which then waits for new file descriptor (socket) to come from
>>the parent. The parent after it spawns several children will wait
>>listen on a socket for new connections which it will pass to one of
>>the child processes after the connection has been established.
>
>

>Depending on the UNIX flavour you use, the passing of open
>filedescriptors is done in different ways. On most System-V-based
>systems, such as Solaris, sending an open fildescriptor in regular
>C-programs requires you to have an open STREAM between the two
>processes (a pipe will do fine most of the time). You then ioctl()
>this stream with a I_SENDFD in the sender end and a I_RECVFD in the
>receiver end, and your filedescriptor will be passed.
>

>I do not know if you can use the fcntl.ioctl() call in Python to do
>the same thing, but my guess is it should work (if you are on a
>machine whose UNIX dialect supports the I_SENDFD/I_RECVFD ioctls, that
>is. Check your local ioctl() manpage). A quick glance at the Python
>interpreter's source-code seems to indicate that this _should_ work,
>but I'm not certain.
>

>I suppose the best thing would be to actually try it. :-) IPC in UNIX
>isn't exactly a breeze to get working properly, and sending
>open fildescriptors between otherwise unrelated processes is not the
>easiest form of IPC there is, either.
>

>If I get the time tonight, I'll give it a shot and see if I can make
>it work.
>
>

>/Viktor...


I'm currently needing the module for a project that will run on a
Linux machine and unfortunately Linux does appear to support the
System-V approach. There is no I_SENDFD or I_RECVFD in the ioctl()
man page. I have found two example code URLs so far and both use the
socketpair() call which isn't in the Python socket module. So I'm
assuming my only option is to write a C module to do this? If I can
get it done it might be a nice compliment to my only other C module
appropriately named hello_world. ;-)

http://www2.awl.com/cseng/books/lad/src/passfd.c
http://x26.deja.com/getdoc.xp?AN=443099042

-Dan


Guido van Rossum

unread,
Oct 2, 1999, 3:00:00 AM10/2/99
to
puter_pr...@yahoo.com (Dan) writes:

> I'm currently needing the module for a project that will run on a
> Linux machine and unfortunately Linux does appear to support the
> System-V approach. There is no I_SENDFD or I_RECVFD in the ioctl()
> man page. I have found two example code URLs so far and both use the
> socketpair() call which isn't in the Python socket module. So I'm
> assuming my only option is to write a C module to do this? If I can
> get it done it might be a nice compliment to my only other C module
> appropriately named hello_world. ;-)

My guess is that those examples use socketpair() because it's
convenient, not because it's necessary. I would guess that creating a
server AF_UNIX socket in the parent and a client AF_UNIX socket in the
child, connecting the latter to the former, would allow you to do the
same thing. You might even be able to do this between unrelated
processes. Of course, this is pure speculation...

--Guido van Rossum (home page: http://www.python.org/~guido/)

Viktor Fougstedt

unread,
Oct 3, 1999, 3:00:00 AM10/3/99
to
Guido van Rossum <gu...@cnri.reston.va.us> writes:

>puter_pr...@yahoo.com (Dan) writes:

>> I'm currently needing the module for a project that will run on a
>> Linux machine and unfortunately Linux does appear to support the
>> System-V approach. There is no I_SENDFD or I_RECVFD in the ioctl()

>My guess is that those examples use socketpair() because it's


>convenient, not because it's necessary. I would guess that creating a
>server AF_UNIX socket in the parent and a client AF_UNIX socket in the
>child, connecting the latter to the former, would allow you to do the
>same thing. You might even be able to do this between unrelated
>processes. Of course, this is pure speculation...

It should definately work without socketpair(), and even between
unrelated processes. But I do not think it will work without
sendmsg()/recvmsg(), which are needed to be able to set the message
type to a proper value. Even then it's kind of a thin line to walk
down, since the msghdr struct looks different on different UNIX
dialects...

But if one were to implement sendmsg()/recvmsg() in the socket module,
which shouldn't be much work I suppose, it is very probable that it
would be possible to pack()/unpack() a struct that could be used to
pass open filedescriptors on Linux. The simplest implementation of
sendmsg()/recvmsg() could just pass a pack()ed struct as a msghdr, and
then it's up to the Python user to make sure it's a correct msghdr.


/Viktor...

Greg Ewing

unread,
Oct 4, 1999, 3:00:00 AM10/4/99
to
Guido van Rossum wrote:
>
> I would guess that creating a
> server AF_UNIX socket in the parent and a client AF_UNIX socket in the
> child, connecting the latter to the former, would allow you to do the
> same thing.

Try using an ordinary pipe first. On BSD-flavoured unices, pipes
are often implemented as a pair of unix-domain sockets.

You'll definitely need sendmsg() and recvmsg(), though, so
you'll still have to write some C.

Greg

Tres Seaver

unread,
Oct 7, 1999, 3:00:00 AM10/7/99
to

In article <37F85D6E...@compaq.com>,

Pipes would be less portable (SysV IPC uses another mechanism). Here is a
rendition of the actual read/write FD code which works on my Linux boxes,
and should work on *BSD as well:

------------------------- cut here -----------------------------------------

include <sys/socket.h>
#include <iovec.h>


typedef struct msghdr MsgHdr;
typedef struct cmsghdr CMsgHdr;
typedef struct iovec IOVec;

typedef union /* force alignment of trailing data */
{
CMsgHdr cm;
char control[ CMSG_SPACE( sizeof( int ) ) ];
} CMsgHdrUnion;


/*
* Pass a descriptor across a Unix domain stream socket. Return number of
* bytes of main payload passed (caller should pass at least one byte, to
* allow peer to detect EOF cleanly).
*
* See W. R. Stevens, Unix Network Programming (2nd ed), vol 1, p. 389.
*
* In "pre-forked child" use case, sockFD may be one end of a socketpair();
* for unrelated servers, should be created by bind()/connect().
*/

ssize_t
write_fd( int sockFD /* AF_UNIX stream socket */
, void* payload /* Payload buffer */
, size_t nPL /* Size of payload */
, int sendFD /* Descriptor to be passed */
)
{
MsgHdr msg; /* sendmsg() buffer */
IOVec iov[1]; /* scatter-gather payload vector */

CMsgHdrUnion controlX;

CMsgHdr* cmptr;

msg.msg_name = NULL; /* name not used for stream sockets. */
msg.msg_namelen = 0;

iov[0].iov_base = payload; /* set up scatter-gather. */
iov[0].iov_len = nPL;
msg.msg_iov = iov;
msg.msg_iovlen = 1;

/*
* XXX: Older Unixen may use msg_accrights instead of msg_control.
*/
msg.msg_control = controlX.control;
msg.msg_controllen = sizeof( controlX.control );

cmptr = CMSG_FIRSTHDR( &msg );
cmptr->cmsg_len = CMSG_LEN( sizeof( int ) );
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;

*( (int*) CMSG_DATA( cmptr ) ) = sendFD;

return( sendmsg( sockFD, &msg, 0 ) );
}


/*
* Receive a descriptor across a Unix domain stream socket. Return number of
* bytes of main payload passed (should be least one byte, to distinguish
* EOF).
*
* See W. R. Stevens, Unix Network Programming (2nd ed), vol 1, p. 387.
*
* In "pre-forked child" use case, sockFD may be one end of a socketpair();
* for unrelated servers, should be created by bind()/connect().
*/
ssize_t
read_fd( int sockFD /* AF_UNIX socket over which to pass descriptor */
, void* payload /* Payload buffer */
, size_t maxPL /* Size of payload buffer */
, int* recvFD /* Address of descriptor being received */
)
{
MsgHdr msg; /* recvmsg() message buffer */
IOVec iov[1]; /* Scatter-gather payload vector */
ssize_t nPL; /* Size of actual payload received */
CMsgHdrUnion controlX;

CMsgHdr* cmptr;

msg.msg_name = NULL; /* name not used for stream sockets */
msg.msg_namelen = 0;

iov[0].iov_base = payload; /* set up scatter-gather */
iov[0].iov_len = maxPL;
msg.msg_iov = iov;
msg.msg_iovlen = 1;

/*
* XXX: Older Unixen may use msg_accrights instead of msg_control.
*/
msg.msg_control = controlX.control;
msg.msg_controllen = sizeof( controlX.control );

/*
* XXX: Stevens does'nt pre-initialize cm, but RH 5.2 acts flaky
* without it.
*/
cmptr = CMSG_FIRSTHDR( &msg );
cmptr->cmsg_len = CMSG_LEN( sizeof( int ) );
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;

*( (int*) CMSG_DATA( cmptr ) ) = -1;

nPL = recvmsg( sockFD, &msg, 0 );

if ( nPL <= 0 )
return( nPL );

/*
* XXX: Might realloc, so reseat?
*/
cmptr = CMSG_FIRSTHDR( &msg );

if ( cmptr != NULL
&& cmptr->cmsg_len == CMSG_LEN( sizeof( int ) )
&& cmptr->cmsg_level == SOL_SOCKET
&& cmptr->cmsg_type == SCM_RIGHTS
)
*recvFD = *( (int*) CMSG_DATA( cmptr ) );
else
*recvFD = -1; /* descriptor was not passed */

return( nPL );

0 new messages