Search code examples
cunixpipeforkchild-process

Bad file descriptor error when closing pipe file descriptors after forking


I try to make 2 children of process to communicate:

void demo() {
    int pipes[2];
    int create_pipe_status = pipe(pipes);
    int status;
    pid_t fork_id = fork();
    if(!fork_id) {
        //parent

        status = dup2(pipes[1], STDOUT_FILENO);
        close(pipes[0]);
        close(pipes[1]);
    }

    pid_t fork_id2 = fork();
    if(!fork_id2){
        //child
        status = dup2(pipes[0], STDIN_FILENO);
        close(pipes[0]);
        close(pipes[1]); //keep
    }
    wait(NULL);
    wait(NULL);
}

and when I try to close the file descriptors associated with the pipe I get the error EBADF (bad file descriptor) in errno.

What is my mistake?


Solution

  • First, the existing process calls fork(), then this original process and its forked child both call fork() again resulting in four processes in total. That is, the processes are:

    1. The original process.
    2. The first child of the original process - created by the original process with the first call to fork().
    3. The second child of the original process - created by the original process with the second call to fork().
    4. The child process of the child process – created by the child process with the second call to fork().

    As a result, the code block (i.e., the second if block) is executed by the child process of the child process:

    if(!fork_id2){
        //child
        status = dup2(pipes[0], STDIN_FILENO);
        close(pipes[0]);
        close(pipes[1]);
    }
    

    This child process of the child process tries to close pipe[0] and pipe[1]. However, the child process (i.e., the parent process of the child of the child) has already closed both pipe[0] and pipe[1]. This is where the bad file descriptor error comes from, i.e., closing the file descriptor twice.

    Note that even though the second child of the original process also executes this code block, it doesn't suffer from this issue because the original process hasn't closed the pipe file descriptors at the moment of calling fork() for the second time, so both file descriptors are still open when the second child of the original process closes them.


    I guess what you actually want is two create two children processes from an existing process. If so, then just limit the second call to fork() to the parent process (i.e., the original process):

    void demo() {
        int pipes[2];
        int create_pipe_status = pipe(pipes);
        int status;
    
        pid_t fork_id = fork();
        if (!fork_id) { // 1st child
            status = dup2(pipes[1], STDOUT_FILENO);
            assert(!close(pipes[0]));
            assert(!close(pipes[1]));
        } else {
          // parent
          pid_t fork_id2 = fork();
          if (!fork_id2) { // 2nd child
            status = dup2(pipes[0], STDIN_FILENO);
            assert(!close(pipes[0]));
            assert(!close(pipes[1]));
          } else { // parent
            assert(!close(pipes[0]));
            assert(!close(pipes[1]));
          }
        }
        wait(NULL);
        wait(NULL);
    }