Aiman
Aiman

Reputation: 69

In C, why is my program stuck on wait() when trying to pipe multiple commands?

I'm writing a simple program to pipe between three commands. For each command, I fork a child and call execvp in it, while the parent waits for execvp to finish (Note since I fork three times, I call wait(NULL) three times)

I've created two pipes, pipe_A and pipe_Z. The flow is something like:

Command 1 writes its output to pipe_A, command 2 reads input from pipe_A, then writes output to pipe_Z, then command 3 reads input from pipe_Z, then writes to standard output.

The problem is that the program gets stuck on the second wait(NULL). In my code, it's the line right after 'printf("reached\n");'. The program doesn't terminate, and is stuck until I send an interrupt.

If I replace all three commands with the same command "echo hello", it doesn't do much but the program ends at least.

I've written a smaller program (same structure) with only two commands ("echo hello" and "wc") and it also gets stuck, just on the first wait(NULL). However, I've noticed that if I call execvp on the second command in the main program, the output is printed as expected. However, the program also terminates immediately (because of execvp), so this isn't how I want to do it.

I suspect I'm making a very obvious mistake because of lack of understanding either how pipes work or how the wait(NULL) function call works.

int main(){

//### COMMANDS AS STRING ARRAYS ###

    char * cmds1[3] = {"echo","hello", NULL};
    char * cmds2[3] = {"cat","-",NULL};
    char * cmds3[3] = {"wc", NULL};

//### CREATING PIPES ###

    int pipe_A[2];
    int pipe_Z[2];

    pipe(pipe_A);
    pipe(pipe_Z);

//### FIRST FORK FOR FIRST EXECVP ###

    int pid = fork();

    //Child process
    if(pid==0){

        close(pipe_A[0]);
        dup2(pipe_A[1], STDOUT_FILENO);

        execvp(cmds1[0],cmds1);
        exit(1);

    }

    //Parent process
    else if(pid >0){
        wait(NULL);
    }

//### SECOND FORK FOR SECOND EXECVP ###

    int pid2 = fork();

    //Child process
    if(pid2==0){

        close(pipe_A[1]);
        dup2(pipe_A[0],STDIN_FILENO);

        close(pipe_Z[0]);
        dup2(pipe_Z[1], STDOUT_FILENO); 

        execvp(cmds2[0],cmds2);
        exit(1);

    }

    //Parent process
    else if(pid2>0){

        printf("reached\n");
        wait(NULL);

    }

//### THIRD FORK FOR THIRD EXECVP ###

    int pid3 = fork();

    //Child process
    if(pid3==0){

        close(pipe_Z[1]);
        dup2(pipe_Z[0],STDIN_FILENO);

        execvp(cmds3[0],cmds3);
        exit(1);

    }

    //Parent process
    else if(pid2 >0){

        wait(NULL);

    }


//### END OF PROGRAM ###

    return 0;

}

Expected output is "1 1 6", but program does not terminate.

Upvotes: 0

Views: 2256

Answers (2)

Luis Colorado
Luis Colorado

Reputation: 12668

you say:

... while the parent waits for execvp to finish

and wait(2) never waits for the execvp(2) system call to finish, but to the whole program started by execvp(2) to finish.

BTW, execvp(2) doesn't return in case of no error. A good thing is to show in stderr why it failed, instead of making a exit(1), because the exit code is sheldom checked by a user, and you'll not know what happened. Something like

execvp(...);
fprintf(stderr, "EXECVP: %s: %s\n", cmds?[0], strerror(errno));
exit(1);

would be far much effective. It just starts another program in the current process, the child you fork()ed from the parent.

  • Your first problem is that you leave the unused file descriptors open in the parent, so the reader process of a pipe will get stuck waiting for more input until the writing side of the pipe gets closed by all processes that have it open for writing (and this includes the parent). You create the pipes in the parent process, and so, you get them also on the children. In the children you close(2) the not used side of the pipe, but you don't do the same in the parent (and you must, before the wait) so the reading children will receive the end of file when the writer side closes the writing side, because then, there's no more writer processes (like now is the parent)

  • Second, this is a simple case in which the three processes will forcely end in sequence, but even in this case, you are waiting for them to finish before launching the next processes. As processes are interrelated, only after them all have had a chance to exchange info. In that case, you are waiting for one to finish before launching the second, and the second will not be reading anything until it is alive. In your case, the first wait(2) is waiting for the first child to exit, but the first child cannot close(2) (it is blocked in the close) because nobody has opened the pipe for reading (well, the parent did, but the process expected to be reading that info is the second child). If you wait for the first process to die, both processed will never be alive at the same time, because you wait for the first to end before spawning the second.

The better is to wait for them all at the end... as you say, you have made three successful fork()s, so you have to do three wait()s to properly end. In that case

for (i = 0; i < 3; i++) wait(NULL);

is correct (but at the end). (even if any fork was not successful, the wait will detect this and fail with an error, so you don't have to check for errors)

Upvotes: 1

You're not closing the write ends of the pipes in the parent process, so none of them ever really close all the way, so none of the children ever get EOF, so none of the children (except echo which doesn't read input) ever exit.

Upvotes: 3

Related Questions