Calling fclose() here after dup()ing its file descriptor blocks until the child process has ended (presumably because the stream has ended).
FILE *f = popen("./output", "r");
int d = dup(fileno(f));
fclose(f);
However by manually performing the pipe(), fork(), execvp() of the popen(), and then dup()ing the pipe’s read file descriptor, closing the original does not block.
int p[2];
pipe(p);
switch (fork()) {
case 0: {
char *argv[] = {"./output", NULL};
close(p[0]);
dup2(p[1], 1);
execvp(*argv, argv);
}
default: {
close(p[1]);
int d = dup(p[0]);
close(p[0]);
}
}
Why does this occur, and how can I close the FILE * returned from popen() and use a file descriptor in its place?
Update:
I’m aware that the documentation says to use pclose(), however fclose() blocks as well. Furthermore, I poked around in the glibc code, and pclose() just calls fclose(). The behaviour is the same, whether fclose() or pclose() is used.
Disappointed with the generality of the answers so far (I can RTFM, tyvm), I’ve investigated this thoroughly, by stepping through and reading the glibc source.
In glibc
pclose()directly callsfclose()with no additional effect, so the 2 calls are same. In fact you could usepclose()andfclose()interchangeably. I’m sure this is purely a coincidence in the evolved implementation, and the use ofpclose()to close aFILE *returned frompopen()is still recommended.The magic is in
popen().FILE *s in glibc contain a jump table with pointers to appropriate functions to handle such calls asfseek(),fread(), and of relevancefclose(). When callingpopen(), a different jump table used than the one used byfopen(). Theclosemember in this jump table points to a special function_IO_new_proc_close, which callswaitpid()on the pid stored in the region pointed to byFILE *.Here’s the relevant call stack in my version of glibc, which I’ve annotated with notes about what is going on:
So the short of it is, using
popen(), the returnedFILE *must not be closed, even if youdup()its file descriptor, because it will block until the child process terminates. Of course, after this you’ll be left with a file descriptor to a pipe which will contain whatever the child process managed to write() to it before terminating.By not
fread()ing with the file pointer returned frompopen(), the underlying pipe will not be touched, it’s safe to use the file descriptor byfileno(), and finish up by callingpclose().