Search code examples
cunixpipepipeline

c Pipes causing program to hang in some cases


I'm trying to use pipes to link the stdout of 1 command exec'd to the stdin of another. Eg mimic (cmd1 | cmd2)

Below is a heavily stripped down version of my code.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main (int argC, char *argv[])
{

    //Run first command
    int fds[2];
    if (pipe (fds) < 0) {       //Create pipe
        fprintf (stderr, "Pipe Failed\n");
    }

    int pid;
    if ((pid = fork ()) == -1) {
        fprintf (stderr, "Fork 1 Failed\n");
        exit (1);
    }

    if (pid == 0) {             //First child proccess
        close (fds[0]);         //Close input end of pipe
        dup2 (fds[1], STDOUT_FILENO);   //Set stdout to output pipe
        close (fds[1]);         //Close output end of pipe

        fprintf (stderr, "Exec 1 executing now\n");
        execlp ("./addone", "./addone", NULL);  //execute first command - Doesnt cause hang
        //execlp("ls", "ls", NULL);//Causes hang
        fprintf (stderr, "Exec 1 failed\n");
    } else {                    //First parent segment
        int returnStatus;
        waitpid (pid, &returnStatus, 0);        //Wait for child 1 to finish

        fprintf (stderr, "Back to parent 1\n");
    }

    //Run second command
    if ((pid = fork ()) == -1) {
        fprintf (stderr, "Fork 2 failed\n");
    }

    if (pid == 0) {             //second child proccess
        dup2 (fds[0], STDIN_FILENO);    //Set stdin to input pipe
        close (fds[0]);         //Close input end of pipe
        close (fds[1]);         //Close output end of pipe

        fprintf (stderr, "Exec 2 executing now\n");
        execlp ("./addone", "./addone", NULL);  //execute first command - Doesnt cause hang
        //execlp("wc", "wc", NULL);//Causes hang
        fprintf (stderr, "Exec 2 failed\n");
    } else {                    //second parent segment
        int returnStatus;
        waitpid (pid, &returnStatus, 0);        //Wait for child 2 to finish

        fprintf (stderr, "Back to parent 2\n");
        //Done with pipes
        close (fds[0]);
        close (fds[1]);
    }
    return 0;
}

In the program I attempt to make a pipe and route the first execs stdout to the seconds stdin. When attempting to run the program using ls | wc as my execs my program hangs on the 2nd exec. However when I use a simple program "./addone" as my execs the entire execution finishes properly.

Where "addone" is a small program:

#include<stdio.h>

int main(){
    int value = 0;
    scanf("%d", &value);
    value++;
    printf("%d\n", value);
}

Its been suggested that my problem may be with there being an input pipe being left open but I cant work out where that would happen, and it doesn't explain why my program runs perfectly when using my simple test program.

Any advice as to what would be causing this hang would be greatly appreciated.


Solution

  • I don't know if you got your problem solved or not, but continuing from the comments, if you are going to build a pipeline, the key is understanding that if you write to a pipe in one process, you must read from that same pipe in the next process, and write to a 2nd pipe if you are to continue the pipeline. Now you can automate the process as shown in the link C Minishell Adding Pipelines, but for learning purposes, it is probably easier to understand if you just work through your single linear example.

    Below is the implementation of your code using two file descriptors fd1 and fd2. Your addone process will first read from plain old stdin, so there is no manipulating of file descriptors for its read. However, for its write, it must use the write-end (for lack of better words) of fd1 to pipe its output to the next child process.

    The next child process must read from the input-end of that same fd1 to receive its input and it must write to the output-end of another file descriptor fd2. After both childs have exited, then the parent process must read from where?? (What was the last pipe written too...? Ahah!) fd2. So the parent dups the read-end of fd2 (closing all other unused ends) and read the final answer of how many addone added to the initial number.

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    int main (void) {
    
        int fd1[2], fd2[2];  /* file descriptors 1 & 2 */
        int pid;
    
        if (pipe (fd1) < 0 || pipe (fd2) < 0) { /* open both pipes */
            fprintf (stderr, "pipe creation failed\n");
        }
    
        if ((pid = fork ()) == -1) {
            fprintf (stderr, "fork 1 failed\n");
            exit (1);
        }
    
        if (pid == 0) {                     /* first child */
            dup2 (fd1[1], STDOUT_FILENO);   /* dup write-end of 1st */
            close (fd1[0]);                 /* close all others */
            close (fd2[0]);
            close (fd2[1]);
    
            fprintf (stderr, "Exec 1 executing now\n");
            execlp ("./addone", "./addone", NULL);
            fprintf (stderr, "Exec 1 failed\n");
        }
        else {
            int returnStatus;
            waitpid (pid, &returnStatus, 0);
            fprintf (stderr, "Back to parent 1\n");
        }
    
        if ((pid = fork ()) == -1) {
            fprintf (stderr, "Fork 2 failed\n");
        }
    
        if (pid == 0) {                     /* second child */
            dup2 (fd1[0], STDIN_FILENO);    /* dup read-end of 1st  */
            dup2 (fd2[1], STDOUT_FILENO);   /* dup write-end of 2nd */
            close (fd1[1]);                 /* close all others */
            close (fd2[0]);
    
            fprintf (stderr, "Exec 2 executing now\n");
            execlp ("./addone", "./addone", NULL);
            fprintf (stderr, "Exec 2 failed\n");
        }
        else {
            int returnStatus;
            waitpid (pid, &returnStatus, 0);
            fprintf (stderr, "Back to parent 2\n");
        }
    
        dup2 (fd2[0], 0);   /* dup read-end of 2nd */
        close (fd2[1]);     /* close all others */
        close (fd1[0]);
        close (fd1[1]);
    
        int total;
        scanf ("%d", &total);
        printf ("The total was: %d\n\n", total);
    
        close (fd2[0]);
    
        return 0;
    }
    

    Example Use/Output

    $ echo 10 | ./bin/pipeaddone
    Exec 1 executing now
    Back to parent 1
    Exec 2 executing now
    Back to parent 2
    The total was: 12
    

    Look it over and let me know if you have any questions. Once you get it, it makes sense. If you don't use it for a while, don't worry, you'll get to re-learn it all over again :)