Question

I'm learning to use pipes and following along with this code on pipes. The program makes two child processes using fork. The first child runs 'ls' command and outputs to pipe1. The second reads from pipe1 runs 'wc' and outputs to stdout.

I'm just trying to add a third process in the middle that reads from pipe1 and outputs to pipe2. Basically what I'm trying to do

  ls | cat | wc -l

What I'm trying to do:

(ls)stdout -> pipe1 -> stdin(cat)stdout-> stdin(wc -l) -> stdout

Nothing ever prints to stdout and the program never exits.

Here's my code with the changes for process #3

int
main(int argc, char *argv[])
{
    int pfd[2];                                     /* Pipe file descriptors */
    int pfd2[2];

    if (pipe(pfd) == -1)                            /* Create pipe */
        perror("pipe");

    if (pipe(pfd2) == -1)                            /* Create pipe */
        perror("pipe");

    /*
    Fork process 1 and exec ls command
    write to pfd[1], close pfd[0]
    */
    switch (fork()) {
    case -1:
        perror("fork");

    case 0:             
        if (close(pfd[0]) == -1)                   
            perror("close 1");

        // dup stdout on pfd[1]
        if (pfd[1] != STDOUT_FILENO) {            
            if (dup2(pfd[1], STDOUT_FILENO) == -1)
                perror("dup2 2");
            if (close(pfd[1]) == -1)
                perror("close 4");
        }
        execlp("ls", "ls", (char *) NULL);          
        perror("execlp ls");
    default:            
        break;
    }

    /*
    *   Fork process 2 and exec wc command
    read from pfd[0], close pfd[1]
    write to pfd[1], close pfd2[0]
    */
    switch (fork()) {
    case -1:
        perror("fork");
    case 0:  
        // read from pfd[0]           
        if (close(pfd[1]) == -1)                  
            perror("close 3");

        if (pfd[0] != STDIN_FILENO) {            
            if (dup2(pfd[0], STDIN_FILENO) == -1)
                perror("dup2 2");
            if (close(pfd[0]) == -1)
                perror("close 4");
        }

        if (pfd2[1] != STDOUT_FILENO) {            
            if (dup2(pfd2[1], STDOUT_FILENO) == -1)
                perror("dup2 2");
            if (close(pfd2[1]) == -1)
                perror("close 4");
        }

        execlp("cat", "cat", (char *) NULL);
        perror("execlp cat");
    default:
        break;
    }

    /*
    *   Fork process 3
    */
    switch (fork()) {
    case -1:
        perror("fork");
    case 0:          
        if (close(pfd2[1]) == -1)                  
            perror("close 3");

        if (pfd2[0] != STDIN_FILENO) {            
            if (dup2(pfd2[0], STDIN_FILENO) == -1)
                perror("dup2 2");
            if (close(pfd2[0]) == -1)
                perror("close 4");
        }

        execlp("wc", "wc", "-l", (char *) NULL);
        perror("execlp wc");
    default:
        break;
    }


    /* Parent closes unused file descriptors for pipe, and waits for children */

    if (close(pfd[0]) == -1)
        perror("close 5");
    if (close(pfd[1]) == -1)
        perror("close 6");
    if (close(pfd2[0]) == -1)
        perror("close 5");
    if (close(pfd2[1]) == -1)
        perror("close 6");

    if (wait(NULL) == -1)
        perror("wait 1");
    if (wait(NULL) == -1)
        perror("wait 2");
    if (wait(NULL) == -1)
        perror("wait 3");

    exit(EXIT_SUCCESS);
}
Was it helpful?

Solution

The problem is that you did not close pfd[1] in process 3, add close(pfd[1]); after case 0 in that process 3 will fix it.

In process 3, that cat will read from pfd[0], however there are four pfd[1] in those processes:

  1. process 0

    this is the main process, pfd[1] in this process will be closed by that close before wait().

  2. process 1

    after ls finished, pfd[1] in this process will be closed automatically by the operating system.

  3. process 2

    pfd[1] has been closed before executing cat.

  4. process 3

    pfd[1] is open in this process while wc is running, and this is what happened at that moment:

    1. in process 2, cat tries to read pfd[0] for data from pfd[1]
    2. in process 3, wc tries to read pfd2[0] for data from pfd2[1]
    3. because pdf[1] still open in process 3, and nothing will be written to it, reading from pfd[0] in process 2 (cat) will wait forever
    4. because cat in process 3 still alive, reading from pfd2[0] in process 3 (wc) will wait (forever)

As you can see, you have a deadlock between process 2 (cat) and process 3 (wc) because of file descriptor leak. To break this deadlock, you just need to close pfd[1] in process 3 before you run wc, after that:

  1. cat in process 2 will exit after ls in process 1 exits, because there is nothing left for it (cat) to read
  2. after cat in process 2 exits, wc in process 3 will also exit, because there is nothing left for it (wc) to read
  3. after that, the main process (parent process) will exit, and the program will finish.

It is possible that there are more than one write ends for the read end of a pipe, unless all these write ends are closed, end-of-file will not be delivered to the read end, and the reader will just wait for more data to come. If there is nothing to come, that reader will wait forever.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top