tags:

views:

6875

answers:

2

I am confused about how popen() redirects stdin, stdout and stderr of the child process in unix. The man page on popen() is not very clear in this regard. The call

FILE *p = popen("/usr/bin/foo", "w");

forks a child process and executes a shell with arguments "-c", "/usr/bin/foo", and redirects stdin of this shell (which is redirected stdin of foo), stdout to p. But what happens with stderr? What is the general principle behind it?

I noticed that, if I open a file in foo (using fopen, socket, accept etc.), and the parent process has no stdout, it gets assigned the next available file number, which is 1 and so on. This delivers unexpected results from calls like fprintf(stderr, ...).

It can be avoided by writing

FILE *p = popen("/usr/bin/foo 2>/dev/null", "w");

in the parent program, but are their better ways?

+2  A: 

The return value from popen() is a normal standard I/O stream in all respects save that it must be closed with pclose() rather than fclose(3). Writing to such a stream writes to the standard input of the command; the command's standard output is the same as that of the process that called popen(), unless this is altered by the command itself. Conversely, reading from a "popened" stream reads the command's standard output, and the command's standard input is the same as that of the process that called popen().

From its manpage, so it allows you to read the commands standard output or write into its standard input. It doesn't say anything about stderr. Thus that is not redirected.

If you provide "w", you will send your stuff to the stdin of the shell that is executed. Thus, doing

FILE * file = popen("/bin/cat", "w");
fwrite("hello", 5, file);
pclose(file);

Will make the shell execute /bin/cat, and pass it the string "hello" as its standard input stream. If you want to redirect, for example stderr to the file "foo" do this first, before you execute the code above:

FILE * error_file = fopen("foo", "w+");
if(error_file) {
    dup2(fileno(error_file), 2);
    fclose(error_file);
}

It will open the file, and duplicate its file-descriptor to 2, closing the original file descriptor afterwards.

Now, if you have your stdout closed in your parent, then if the child calls open it will get 1, since that's (if stdin is already opened) the next free file-descriptor. Only solution i see is to just use dup2 and duplicate something into that in the parent, like the above code. Note that if the child opens stdout, it will not make stdout open in the parent too. It stays closed there.

Johannes Schaub - litb
thanks. sorry for my mistake regarding stdout (I must have been asleep). actually, i now see that i should rephrase my question: what happens if the parent process itself has no stdout in the former case, or no stdin in the latter, or no stderr in both cases?
Dong Hoon
If you are writing to popen, and if stdout was closed in the parent, then actually if the child tries to write to stdout, it will behave like you would try to write to filedescriptor 4211: would not work
Johannes Schaub - litb
thanks. I conclude that it is not safe to rely on the stdin, stdout, stderr in a program that may be used in a command pipe by another program. that is, unless you control it yourself through manual redirection (with dup2() for instance).
Dong Hoon
+5  A: 

popen(3) is just a library function, which relies on fork(2) and pipe(2) to do the real work.

However pipe(2) can only create unidirectional pipes. To send the child process input, and also capture the output, you need to open two pipes.

If you want to capture the stderr too, that's possible, but then you'll need three pipes, and a select loop to arbitrate reads between the stdout and stderr streams.

There's an example here for the two-pipe version.

Alnitak