orangepixel
orangepixel

Reputation: 115

Why does my C program of a pipeline with two pipes hang?

I am trying to write a C program that does something like the pipeline ls | wc | wc. I already did it for ls | wc, and it worked fine, but I can't figure out why my program stops at the child process in the indicated line.

int main (void)
{
    pid_t pid_fils, pid_pfils;

    int fd[2], fd2[2];

    if(pipe(fd)==-1 || pipe(fd2)==-1)
    {
        printf("pipe failed!");
        return 1;
    }

    printf("program started\n");
    pid_fils=fork();
    if(pid_fils==0)
    {
        pid_pfils=fork();
        if(pid_pfils==0)
        {
            //action3
            printf("I am the grandson\n");
            close(fd[0]);//close read side
            dup2(fd[1],1);//connect write with stdout
            close(fd[1]);//close write side
            execlp("ls","ls",(char*)0);
            //execvp("ls",argv3);
            return 0;/*actions grandson*/
        }
        else
        {
            //action2
            printf("I am the son\n");
            wait();
            printf("son, wait ok\n");
            >close(fd[1]);  //close read side
            >dup2(fd[0],0); //connect write with stdin
            >close(fd[0]);  //close read side
            
            ///////pipe2////
           > close(fd2[0]);  //close read side
            >dup2(fd2[1],1); //connect write with stdout/*it stops here -can't display "ok!"*/
            printf("ok!\n");    
            >close(fd2[1]);  //close write side

            execlp("wc","wc",(char*)0);
            printf("error exec returned!\n");    
            return 0;
        }
    }
    else
    {
        ///action1
        printf("I am the parent\n");
        wait();
        printf("parent,wait ok\n");
        close(fd2[1]);  //close write side, 
        dup2(fd2[0],0); //connect read with stdin
        close(fd2[0]);  //close read side
        execlp("wc","wc",(char*)0);
        return 0;/*the parent*/
    }
    return 1;
}

Upvotes: 0

Views: 1815

Answers (3)

uzluisf
uzluisf

Reputation: 3076

Extending this SO answer, we can create an additional pipe, fork another process, and connect the pipe accordingly.

// two-pipes.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

// C implementation for the `ls -la | wc | wc` pipeline.
int main(int argc, char *argv[]) {
    // We declare two pipes, one for each pair of processes, i.e.,
    // ls -al pipefd1 wc pipefd2 wc
    int pipefd1[2];
    int pipefd2[2];
    pid_t ls_pid, wc_pid1, wc_pid2;

    pipe(pipefd1);
    pipe(pipefd2);

    // CHILD PROCESS: ls
    if ((ls_pid = fork()) == 0) {
        // We close-and-then-connect STDOUT to pipefd1's write end so the
        // process write to the pipe instead of the screen.
        dup2(pipefd1[1], STDOUT_FILENO);

        // Since STDOUT_FILENO also refers to pipefd's write end, we can
        // close this file descriptor; it's no longer needed.
        close(pipefd1[1]);

        // The process doesn't use these file descriptors so we close them.
        close(pipefd1[0]);
        close(pipefd2[0]);
        close(pipefd2[1]);

        // Execute the `ls` command, and exit if errors out.
        if ((execl("/bin/ls", "ls", "-al", (char *) NULL)) < 0) exit(0);
    }
    else if (ls_pid < 0) {
        fprintf(stderr, "failed to fork ls process");
        exit(0);
    }

    // CHILD PROCESS: wc (1)
    if ((wc_pid1 = fork()) == 0) {
        // We close-and-then-connect STDIN and STDOUT to pipefd1's read and
        // and pipefd2's write end so the process read from pipefd1 and write
        // to pipefd2 instead of from the keyboard and to the screen respectively.
        dup2(pipefd1[0], STDIN_FILENO);
        dup2(pipefd2[1], STDOUT_FILENO);

        // Since STDIN_FILENO and STDOUT_FILENO also refer to pipefd1's read
        // end and pipefd2's write end respectively, we can close these file
        // descriptors.
        close(pipefd1[0]);
        close(pipefd2[1]);

        // The process doesn't use these file descriptors so we close them.
        close(pipefd1[1]);
        close(pipefd2[0]);

        // Execute the `wc` command, and exit if errors out.
        if ((execl("/usr/bin/wc", "wc", (char *) NULL)) < 0) exit(0);
    }
    else if (wc_pid1 < 0) {
        fprintf(stderr, "failed to fork wc process");
        exit(0);
    }

    // CHILD PROCESS: wc (2)
    if ((wc_pid2 = fork()) == 0) {
        // We close-and-then-connect STDIN to pipefd2's read end so the process
        // read from the pipe instead of from the keyboard.
        dup2(pipefd2[0], STDIN_FILENO);

        // Since STDIN_FILENO also refers to the pipefd2's read end, we can
        // close this file descriptor; it's no longer needed.
        close(pipefd2[0]);

        // The process doesn't use these file descriptors so we close them.
        close(pipefd1[0]);
        close(pipefd1[1]);
        close(pipefd2[1]);

        // Execute the `wc` command, and exit if errors out.
        if ((execl("/usr/bin/wc", "wc", (char *) NULL)) < 0) exit(0);
    }
    else if (wc_pid1 < 0) {
        fprintf(stderr, "failed to fork wc process");
        exit(0);
    }
    
    // PARENT PROCESS

    // The parent process isn't using the pipes, however these descriptors are
    // another references to the pipe's read and write ends and we must close
    // them. Otherwise, it doesn't send the EOF so the children can continue
    // (children block until all input has been processed).
    close(pipefd1[0]);
    close(pipefd1[1]);
    close(pipefd2[0]);
    close(pipefd2[1]);

    // The parent process waits for the three child processes to finish before
    // exiting.
    int ls_status, wc_status1, wc_status2;
    pid_t ls_wpid = waitpid(ls_pid, &ls_status, 0);
    pid_t wc_wpid1 = waitpid(wc_pid1, &wc_status1, 0);
    pid_t wc_wpid2 = waitpid(wc_pid2, &wc_status2, 0);

    // Return main's status based on whether the parent process waited both
    // child processes successfully. Status based only on `ls_status`.
    return
        ls_pid == ls_wpid && WIFEXITED(ls_status) &&
        wc_pid1 == wc_wpid1 && WIFEXITED(wc_status1) &&
        wc_pid2 == wc_wpid2 && WIFEXITED(wc_status2)
        ? WEXITSTATUS(ls_status)
        : -1;
}

Compiling and running it:

$ ls -la
total 112
drwxr-xr-x   8     256 Nov 20 13:52 .
drwxr-x---+ 91    2912 Nov 20 13:55 ..
-rwxr-xr-x   1   33896 Nov 20 13:48 a.out
-rw-r--r--   1      45 Nov 17 15:33 error.log
-rw-r--r--@  1    2390 Nov 20 09:51 ls-wc-pipes.c
-rw-r--r--   1     116 Nov 17 16:01 out.log
-rw-r--r--@  1    2647 Nov 20 09:49 so-pipes.c
-rw-r--r--@  1    4021 Nov 20 13:52 two-pipes.c
$ gcc two-pipes.c && ./a.out
       1       3      25

Upvotes: 0

DrC
DrC

Reputation: 7698

Make sure you close all unused descriptors. In your case, the easiest solution is to move the creation of pipe(fd) into the first if block (in the first sub-process). The problem is that as long as any process could possibly write to the pipe, the reader won't get EOF and so won't terminate.

if(pipe(fd2)==-1)
{
    printf("pipe failed!");
    return 1;
}

printf("program started\n");
pid_fils=fork();
if(pid_fils==0)
{
    if(pipe(fd)==-1)
    {
        printf("pipe failed!");
        return 1;
    }
    pid_pfils=fork();

I should also mention that you may want to reconsider the wait calls. Not sure what you are intending to do with them but you don't want the "ls" process to block on output because the reader hasn't been started yet.

Upvotes: 4

Spundun
Spundun

Reputation: 4034

dup2(fd2[1],1);

Above line will first close the file at descriptor 1 and then duplicate the decriptor from fd2[1] into 1.

1 is stdout. Meaning that call closed stdout.

printf prints to stdout, meaning printf prints to 1 which is now assigned to the pipe fd2

So your ok went into the pipe and not on the screen.

try

        //action2
        printf("I am the son\n");
        wait();
        printf("son, wait ok\n");
        close(fd[1]);  //close read side
        dup2(fd[0],0); //connect write with stdin
        close(fd[0]);  //close read side

        ///////pipe2////
        int my_terminal_out = dup(1);
        close(fd2[0]);  //close read side
        dup2(fd2[1],1); //connect write with stdout/*it stops here -can't display "ok!"*/
        fprintf(my_terminal_out, "ok!\n");    
        close(fd2[1]);  //close write side

Not tested. Also you should test the rest of your code for similar missteps.

+What DrC said.

Upvotes: 1

Related Questions