Search code examples
cforkchild-process

Visibility of a function's return value after a call to fork()


When using fork() to create a new child process, we get a pid_t back that indicates whether we are in the child process (pid == 0) or parent process (pid > 0) after the call to fork() finishes. I'm having a bit of trouble wrapping my head around that in general, but specifically struggle to find an answer to the following question:

  • When wrapping a call to fork() in a function, will the calling code of that function (ever) see return values from within the child potion of the code after fork()?

To illustrate, see the following function I wrote. I'm not sure if the calling process/code will ever receive one of the -1 return values that could be returned from within the else if (pid == 0) { ... } part of the code.

/*
 * Opens the process `cmd` similar to popen() but does not invoke a shell.
 * Instead, wordexp() is used to expand the given command, if necessary.
 * If successful, the process id of the new process is being returned and the 
 * given FILE pointers are set to streams that correspond to pipes for reading 
 * and writing to the child process, accordingly. Hand in NULL for pipes that
 * should not be used. On error, -1 is returned.
 */
pid_t popen_noshell(const char *cmd, FILE **out, FILE **err, FILE **in)
{
    if (!cmd || !strlen(cmd)) return -1;

    // 0 = read end of pipes, 1 = write end of pipes
    int pipe_stdout[2];
    int pipe_stderr[2];
    int pipe_stdin[2];

    if (out && (pipe(pipe_stdout) < 0)) return -1;
    if (err && (pipe(pipe_stderr) < 0)) return -1;
    if (in  && (pipe(pipe_stdin)  < 0)) return -1;

    pid_t pid = fork();
    if (pid == -1)
    {
        return -1;
    }
    else if (pid == 0) // child
    {    
        // redirect stdout to the write end of this pipe
        if (out)
        {
            if (dup2(pipe_stdout[1], STDOUT_FILENO) == -1) return -1;
            close(pipe_stdout[0]); // child doesn't need read end
        }
        // redirect stderr to the write end of this pipe
        if (err)
        {
            if (dup2(pipe_stderr[1], STDERR_FILENO) == -1) return -1;
            close(pipe_stderr[0]); // child doesn't need read end
        }
        // redirect stdin to the read end of this pipe
        if (in)
        {
            if (dup2(pipe_stdin[0], STDIN_FILENO) == -1) return -1;
            close(pipe_stdin[1]); // child doesn't need write end
        }

        wordexp_t p;
        if (wordexp(cmd, &p, 0) != 0) return -1;

        execvp(p.we_wordv[0], p.we_wordv);
        _exit(1);
    }
    else // parent
    {
        if (out)
        {
            close(pipe_stdout[1]); // parent doesn't need write end
            *out = fdopen(pipe_stdout[0], "r");
        }
        if (err)
        {
            close(pipe_stderr[1]); // parent doesn't need write end
            *err = fdopen(pipe_stderr[0], "r");
        }
        if (in)
        {
            close(pipe_stdin[0]); // parent doesn't need read end
            *in = fdopen(pipe_stdin[1], "w");
        }
        return pid;
    }
}

Solution

  • fork copies the whole call stack, so if one of those return -1 statements are executed in the child, it will be returned to the caller of popen_noshell in the child (as well as in the parent). This is probably not what you want, and they should probably be replaced with _exit(-1)