Does this site look plain?

This site uses advanced css techniques

A common technique in the UNIX world is to create a pair of pipe descriptors, fork a child process, and then map the child's descriptors to its standard input and standard output. This allows the child process to run normally, with stdout and stdin routed to/from the parent process.

Lots of programs do this, but it's often not done correctly, leading to maddening I/O errors that are hard to reproduce. This Tech Tip talks about these issues involved and presents a library function that does this remapping correctly.

UNIX Pipes

The pipe(2) system call returns two file descriptors that form a "pipe", a one-way communication channel with a "read end" and a "write end".

[Visualizing a pipe]

It's possible for a program to use a pipe all by itself, but it's not common.

The "usual" way

A UNIX "pipe" is a pair of file descriptors such that what's written on one shows up as available data on the other one. For a single process this would be silly - writing to yourself? - but when this pipe crosses a process boundary it gets a lot more interesting. To make a two-way communication, you need two pipes (that's four file descriptors).

...

int	writepipe[2] = {-1,-1},	/* parent -> child */
	readpipe [2] = {-1,-1};	/* child -> parent */
pid_t	childpid;

/*------------------------------------------------------------------------
 * CREATE THE PAIR OF PIPES
 *
 * Pipes have two ends but just one direction: to get a two-way
 * conversation you need two pipes. It's an error if we cannot make
 * them both, and we define these macros for easy reference.
 */
writepipe[0] = -1;

if ( pipe(readpipe) < 0  ||  pipe(writepipe) < 0 )
{
	/* FATAL: cannot create pipe */
	/* close readpipe[0] & [1] if necessary */
}

#define	PARENT_READ	readpipe[0]
#define	CHILD_WRITE	readpipe[1]
#define CHILD_READ	writepipe[0]
#define PARENT_WRITE	writepipe[1]

if ( (childpid = fork()) < 0)
{
	/* FATAL: cannot fork child */
}
else if ( childpid == 0 )	/* in the child */
{
	close(PARENT_WRITE);
	close(PARENT_READ);

	dup2(CHILD_READ,  0);  close(CHILD_READ);
	dup2(CHILD_WRITE, 1);  close(CHILD_WRITE);

	/* do child stuff */
}
else				/* in the parent */
{
	close(CHILD_READ);
	close(CHILD_WRITE);

	/* do parent stuff */
}

Descriptor Debacle

The above code works most of the time, but it's not guaranteed to do so: pipes typically allocate the two lowest available file descriptors for putting into the array, and if - for some reason - FD #0 and #1 were both available (as in a daemon process that had previously closed all of its unused files), then it's likely that this code will do the wrong thing.

Looking at the child portion of the code, let's imagine that this surprising file-descriptor allocation happens. What else develops in the code? Let's augment the "easy" FD macros with the imaginary file descriptors allocated:

#define	PARENT_READ	readpipe[0]	/* fd#0 */
#define	CHILD_WRITE	readpipe[1]	/* fd#1 */
#define CHILD_READ	writepipe[0]	/* fd#2 */
#define PARENT_WRITE	writepipe[1]	/* fd#3 */

dup2(CHILD_READ=2,  0);  close(CHILD_READ=2);
dup2(CHILD_WRITE=1, 1);  close(CHILD_WRITE=1);

The first dup2 attaches the CHILD_READ=2 pipe to the standard input (fd#0), but fd#0 is actually PARENT_READ! This has the effect of shutting down the pipe going back to the parent. And if that weren't enough, the next line actually closes fd#0, which is the child read pipe. This process has just closed both of its pipe descriptors leading to the parent - though this is likely very rare, it's also very unhelpful.

Can we do something about this?

Getting it right

The solution here is to pay careful attention to both file descriptors that are to be attached to the stdin and stdout streams. If neither of them is 0 or 1 upon entry, it's just a routine pair of dup2 calls. But if either of the file descriptors are in our "target" range, we have to step much more carefully.

When considering the read and write descriptors, there are only three classes for each one:

Considering that the read and write file descriptors won't ever be the same (in this bidirectional pipe example, at least), we end up with a matrix of all the possible combinations. From this we can plot the actions required in each specific case:

#define DUP2CLOSE(oldfd, newfd)	( dup2(oldfd, newfd), close(oldfd) )
read FD
(should be 0)
write FD
(should be 1)
Action
0 1 nothing - it's already done
>=1 >1 DUP2CLOSE(rfd, 0);
DUP2CLOSE(wfd, 1);
0 >1 DUP2CLOSE(wfd, 1);
>1 1 DUP2CLOSE(rfd, 0);
>1 0 DUP2CLOSE(wfd, 1);
DUP2CLOSE(rfd, 0);
1 0 tmp = dup(wfd); close(wfd);
DUP2CLOSE(rfd, 0);
DUP2CLOSE(tmp, 1);

Download

The C language code that implements this algorithm can be found here:

remap-pipe-fds.c.txt - (.txt for easy download)

Other notes

There are actually quite a few variations on this line of thinking, such as attaching a single read/write descriptor (a bidirectional socket) to both stdin and stdout, or dealing with three separate descriptors that must map to stdin, stdout, and stderr. In order to resolve them, a table such as this must be created and the code derived from it. Though the code is not presented here, the procedure for deriving it is.

In addition, some older UNIX systems don't implement the dup2() system call, which makes this code substantially more complicated - but it's possible. We've actually had to do this before - it's not pretty.