fclose()/pclose() may block on some file pointers

Matt Joiner picture Matt Joiner · Nov 15, 2009 · Viewed 7.7k times · Source

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.

Answer

Southern Hospitality picture Southern Hospitality · Nov 16, 2009

http://linux.die.net/man/3/popen

The pclose() function waits for the associated process to terminate and returns the exit status of the command as returned by wait4().

Since pclose() wants to return the exit status, it has to wait for the child to terminate and generate one. Since fclose() calls pclose(), the same applies to fclose() for you.

If you fork and exec and do the rest yourself, you don't end up calling pclose() (directly or indirectly), so there's no waiting at close time. Note however that unless your program is set to ignore SIGCHLD, your process won't terminate (instead it'll go zombie) until the child does. But at least your cost will run to exit first.