Search code examples
cforkposixsystem-callsstdio

What are the pitfalls of using a stdio.h FILE * stream inherited across a fork?


I wrote a (simplistic) wrapper that executes another process in a child process. The child process closes (or redirects) standard error before it calls exec(). However, in case exec() fails, I want an error message to be printed on the parent process' standard error.

Below is my current solution. I dup()licate standard error before the fork. Since I want to be able to do formatted I/O, I call fdopen() on the duplicated handle. (I know that, as it stands, this program makes little sense, but I have only retained the relevant parts for my question.)

#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int status;
    FILE *log = fdopen(dup(2), "w");
    switch (fork()) {
        case -1:
            fprintf(log, "fork: %s\n", strerror(errno));
            break;
        case 0:
            close(2);
            execvp(argv[1], &argv[1]);
            fprintf(log, "execvp: %s\n", strerror(errno));
            break;
        default:
            wait(&status);
            break;
    }
    return 0;
}

My question is now: what are the pitfalls of using the FILE * variable log, both in the parent and the child process? I've read the IEEE Std 1003.1-2008 Standard, XSH, Sections 2.5 and 2.5.1 ("Interaction of File Descriptors and Standard I/O Streams"), and from what I've understood, this should work, except that I may need to add fflush(log) right before the fork if standard error happened to be buffered. Is that correct? Are there other pitfalls to be aware of?

Also, the same standard states in Section 2.5 that

The address of the FILE object used to control a stream may be significant; a copy of a FILE object need not necessarily serve in place of the original.

Can I take this to mean that a FILE object inherited across fork() is safe to use?

I should add that I do not intend to do anything in the parent process between fork() and wait().


Solution

  • Aside from the need to fflush before the fork, you should also consider the case where the standard error might not be a console/pipe, but a regular file.

    If you write to a regular file from two file descriptors (and that's what your parent and child now have), the writes might overlap each other. If that happens, you may lose information.

    I suggest using fcntl(F_SETFL) to make sure the file descriptor is in O_APPEND mode. In that mode, two processes can write to the same file and not overwrite each other.

    Even in O_APPEND, if the writes are too big, the outputs might overlap. Since you're using buffered IO (which is what FILE* is), this is less likely, but not impossible. At this point your best bet is to just take that chance, and also to fflush relatively often, so that the buffers sent to write are not too big.