I'm working on a program in Rust that for various reasons needs to pass data to a child process using a file descriptor.
This is an odd pattern that exists in some old Unix programs. Normally, a program will accept input in the form of command-line arguments, files, or standard input. But some programs will also take input on an arbitrary file descriptor that you provide them (and tell them about with a command-line argument).
How does one pass data through a file descriptor to another program, you ask? Well, the Unix way of running a child process is to first create a copy of the current process (the
fork system call), and then replace it with whatever other program you want (the
exec system call). In the course of doing this, the child inherits quite a lot of stuff from the parent, including any file descriptors open at the time of the fork, unless the child closes them before doing its
So doing this should be easy, right? Open a file (or make a pipe, or whatever), get its file descriptor number, fork and exec, passing the number as a command-line argument.
Well, not quite. Rust, being a safe-by-default language, sensibly creates all file descriptors with the
FD_CLOEXEC flag, which means they automatically close on an
exec system call, without needing to be explicitly closed. This is usually what you want; you don't generally want to be passing handles to all your open files to whatever random process you run. But Rust doesn't provide a way to turn this off.
Not out of the box, at least. But it's okay! Rust makes interoperating with C code really easy, and in fact there's a crate available with bindings to most of libc out there. So it's a simple matter of using the
fcntl system call to turn that flag off of whatever file descriptor you want your child to inherit.
I made a nice utility class that demonstrates this. It's called
InheritablePipe, and it uses the libc crate to call the
pipe system call, which also doesn't seem to be available in the standard library.
The other nifty thing it does is to take the parent-end of the pipe and create a regular old Rust
std::fs::File instance out of it by using the
std::os::unix::io::FromRawFd trait which is helpfully implemented by
File. Yeah, it's not actually backed by anything on the filesystem, but Rust doesn't really care; it just uses the file descriptor as if it were a real file.
The program I'm invoking is GnuPG, an encryption program. GPG normally invokes 'pinentry' to read your passphrase, or it can read from standard input, or a file, or an arbitrary file descriptor. Using 'pinentry' is unsuitable for my needs because in one of its modes, it takes over your terminal and screws up my program's output. Taking the passphrase from standard input won't work for me because my program is already using standard input to feed GPG the data to be encrypted. Passing the passphrase through a file is horribly insecure, so I won't do that. That leaves the file descriptor method. ↩︎
When a Unix program requests the operating system to access a file, the operating system gives it back a number, which the program and the operating system use to refer to the file from then on. The numbers start at zero and count upwards. This number is called a "file descriptor". File descriptors 0, 1, and 2 are special: they are the so-called "standard input", "standard output", and "standard error", respectively, and they usually refer to the terminal the program is attached to. Programs generally start with these three file descriptors already in place; subsequent files opened start at number 3, and so on. ↩︎