Unable to exit loop after reading using two pipes in C (processes)

488 views Asked by At

I have taken a look at this and also this stack overflow links.

I am having trouble understanding the process for closing write ends of pipes. In the code below, I have 3 processes, one parent, a child of the parent, and a child of the child. I am trying to simulate a pipe for the command - cat xxx | grep 28 | sort. I have written some code for this, and it essentially creates the sorts, "grips"/filters and prints my input, but it hangs at the end. I have to ctrl + c to exit. My code is a little messy, but if you can help me spot the problem that would be nice.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

/**
 * Executes the command "cat scores | grep Lakers".  In this quick-and-dirty
 * implementation the parent doesn't wait for the child to finish and
 * so the command prompt may reappear before the child terminates.
 *
 */

int main(int argc, char **argv)
{
  int pipefd[2];
  int pipefd2[2];
  int pid;

  char *cat_args[] = {"cat", "scores", NULL};
  char *grep_args[] = {"grep", "28", NULL};
  char *sort_args[] = {"sort", NULL};
  

  // make a pipe (fds go in pipefd[0] and pipefd[1])

  if (pipe(pipefd) != 0){
    return 1;
  }

   if (pipe(pipefd2) != 0){
    return 1;
  }

  pid = fork();

  if (pid < 0) 
    { 
    fprintf(stderr, "fork Failed" ); 
    exit(EXIT_FAILURE);
    } 
  else if (pid == 0)
    {     
    
    int pid2;
    pid2 = fork();
    
    if (pid2 < 0){
      fprintf(stderr, "fork Failed" ); 
      return 1;
    }
    else if (pid2 == 0){
      // replace standard input with input part of pipe
//       close(0);
//       close(pipefd[1]);
//       close(pipefd2[1]);
      dup2(pipefd2[0], 0);

      // close unused hald of pipe
      
      close(pipefd2[0]);
      close(pipefd[1]);
      close(pipefd2[1]);
      close(pipefd[0]);

      // execute grep

      execvp("sort", sort_args);
      close(pipefd[1]);
      close(pipefd2[1]);
      exit(0);
    }else{
      // replace standard input with input part of pipe
      
//       close(pipefd[1]);
//       close(pipefd2[1]);
      dup2(pipefd[0], 0);
      dup2(pipefd2[1], 1);

      // close unused hald of pipe
      close(pipefd[0]);
      close(pipefd2[1]);
      close(pipefd[1]);
      close(pipefd2[0]);

      // execute grep

      execvp("grep", grep_args);
      waitpid(pid2, NULL, 0);
      
     
      close(pipefd[1]);
      close(pipefd[0]);
      close(pipefd2[1]);
      close(pipefd2[0]);
      exit(0);
      waitpid(pid2, NULL, 0);
    }
    }
  else
    {
//       close(pipefd[1]);
//       close(pipefd2[1]);
      dup2(pipefd[1], 1);

      // close unused unput half of pipe
      
      close(pipefd[1]);
      close(pipefd[0]);
      close(pipefd2[1]);
      close(pipefd2[0]);
      // execute cat

      execvp("cat", cat_args);
    
    exit(0);
    waitpid(pid, NULL, 0);
    }
  
  close(pipefd[1]);
  close(pipefd[0]);
  close(pipefd2[1]);
  close(pipefd2[0]);

}

here is the output I am getting. Not sure it is relevant but as you can see, the result is sorted by team name. It just doesn't terminate.

Houston       44      28      .611
Indiana 45      28      .616
Oklahoma City   44      28      .611
Utah    44      28      .611
^C
1

There are 1 answers

2
kali On

Calling execvp replaces the current process image with a new process image. If no error occured, your code will never reach any line after that, so your close() and waitpid() function calls are useless.

EDIT

Here's a fully functional code to your problem. The comments should be self explanatory. Notice that the command executing order is different and I'm waiting for processes to finish.

Reading from an empty pipe will block until there is some data to read or an error occured, so this is not the only solution.

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/wait.h>
    static void die (const char *msg) {
        perror (msg);
        exit (EXIT_FAILURE);
    }
    
    int main (int argc, char** argv) {
        int pipefd[2];
        int pid;
        char *cat_args[] = {"cat", "scores", NULL};
        char *grep_args[] = {"grep", "28", NULL};
        char *sort_args[] = {"sort", NULL};
    
        //make a pipe (file descriptor to read is pipefd[0], fd to write is pipefd[1])
        if (pipe (pipefd) < 0) 
            die ("creating a pipe failed");
        pid = fork();
        if (pid < 0)
            die ("fork");
        else if (pid == 0) {
        //child process
        int pipefd2[2]; //only visible to the affected processes
        if (pipe (pipefd2) < 0)
            die ("pipe");
        int pid2;
        pid2 = fork();
        if (pid2 < 0)
            die ("fork");
        else if (pid2 == 0) {
            //child of child will execute cat command
            close (pipefd2[0]); //we only need to write to the second pipe. close its reading end
    
            //first pipe is for communication between parent and grandparent only
            close (pipefd[0]);
            close (pipefd[1]);
    
            dup2 (pipefd2[1], STDOUT_FILENO); //write the output to the second pipe instead of the standard output
            close (pipefd2[1]); //close writing end of second pipe
    
            execvp("cat", cat_args); //execute cat command
            die ("execvp should never return");
    
        }
        else {
            //child process will execute the grep command
            close (pipefd2[1]); // we only need to read from the second pipe. close its writing end
            close(pipefd[0]); //we won't read from the first pipe
    
    
            waitpid (pid2, NULL, 0); //wait for cat command to finish
    
            dup2 (pipefd2[0], STDIN_FILENO); //read from the second pipe instead of the stdin
            close (pipefd2[0]); //child finished. close reading end of second pipe
    
            dup2 (pipefd[1], STDOUT_FILENO); //write the results of grep command to first pipe instead of standard output
            close (pipefd[1]); //we dup2 the output, close the writing end of first pipe
    
            execvp ("grep", grep_args);
            die ("execvp should never return");
    
        }
        } else {
        //parent process will execute the sort command
        close (pipefd[1]); //we won't write to the first pipe
    
    
        waitpid (pid, NULL, 0); //wait for child to write grep output to the first pipe
    
        dup2 (pipefd[0], STDIN_FILENO); //read from the first pipe instead of stdin
        close (pipefd[0]); //child finished. close reading end of first pipe
    
        execvp ("sort", sort_args); //execute command
        die ("execvp should never return");
        }
        //exit (EXIT_SUCCESS); we don't need this. the programm will never reach this line
    }