Reputation: 1
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
Upvotes: 0
Views: 506
Reputation: 159
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
}
Upvotes: 1