Recommended

2013-08-13

How to send Unix file descriptors between processes?

This blog post explains how to send Unix file descriptors between processes. The example shown below sends from parent to child, but the general idea works the other way round as well, and it works even between unrelated processes.

As a dependency, the processes need to have the opposite ends of a Unix domain socket already. Once they have it, they can use sendfd to send any file descriptor over the existing Unix domain socket. Do it like this in Python:

#! /usr/bin/python"""sendfd_demo.py: How to send Unix file descriptors between processesfor Python 2.x at Tue Aug 13 16:07:29 CEST 2013"""import_multiprocessingimportosimportsocketimportsysimporttracebackdefrun_in_child(f,*args):pid=os.fork()ifnotpid:# Child.try:child(*args)except:traceback.print_exc()os._exit(1)finally:os._exit(0)returnpiddefchild(sb):assertsb.recv(1)=='X'# Be careful: there is no EOF handling in# _multiprocessing.recvfd (because it's buggy). On EOF, it# returns an arbitrary file descriptor number. We work it around by doing# a regular sendall--recv pair first, so if there is an obvious reason for# failure, they will fail properly.f=os.fdopen(_multiprocessing.recvfd(sb.fileno()))printrepr(f.read())printf.tell()# The file size.defmain(argv):sa,sb=socket.socketpair(socket.AF_UNIX,socket.SOCK_STREAM)pid=run_in_child(child,sb)# We open f after creating the child process, so it could not inherit f.f=open('/etc/hosts')sa.sendall('X')_multiprocessing.sendfd(sa.fileno(),f.fileno())# Send f to child.assert(pid,0)==os.waitpid(pid,0)# The file descriptor is shared between us and the child, so we get the file# position where the child has left off (i.e. at the end).printf.tell()print'Parent done.'if__name__=='__main__':sys.exit(main(sys.argv))

It works even for unrelated processes. To try it, run the following program with a command-line argument in a terminal window (it will start the server), and in another terminal window run it several times without arguments (it will run the client). Each time the client is run, it connects to the server, and sends a file descriptor to it, which the server reads. The program:

#! /usr/bin/python2.7"""sendfd_unrelated.py: Send Unix file descriptors between unrelated processes.for Python 2.x at Tue Aug 13 16:26:27 CEST 2013"""import_multiprocessingimporterrnoimportosimportsocketimportstatimportsysimporttimeSOCKET_NAME='/tmp/sendfd_socket'defserver():print'Server.'ssock=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)while1:try:ssock.bind(SOCKET_NAME)breakexceptsocket.error,e:ife[0]!=errno.EADDRINUSE:raise# TODO: Fail if the old server is still running.print'Removing old socket.'os.unlink(SOCKET_NAME)ssock.listen(16)while1:print'Accepting.'sock,addr=ssock.accept()print'Accepted.'assertsock.recv(1)=='X'f=os.fdopen(_multiprocessing.recvfd(sock.fileno()))printrepr(f.read())printf.tell()# The file size.delsock,fdefclient():print'Client.'sock=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)while1:try:sock.connect(SOCKET_NAME)breakexceptsocket.error,e:ife[0]notin(errno.ENOENT,errno.ECONNREFUSED):raiseprint'Server not listening, trying again.'time.sleep(1)print'Connected.'f=open('/etc/hosts')sock.sendall('X')_multiprocessing.sendfd(sock.fileno(),f.fileno())# Send f to server.assert''==sock.recv(1)# Wait for the server to close the connection.delsockprintf.tell()defmain(argv):iflen(argv)>1:server()else:client()print'Done.'if__name__=='__main__':sys.exit(main(sys.argv))

There is no system call named sendfd or recvfd. They are implemented in terms of the sendmsg and recvmsg system calls. Passing the correct arguments is tricky and a bit awkward. Here is how to do it in C or C++: