I need to do my own shell implementation but I am not able to correctly implement pipes.
If I have a command like ls -l | grep toto
I need to redirect the output (stdout) of ls -l
to input (stdin) of grep toto
.
I also want to display the result of the command in the parent and not directly in the child on the call of execvp (that's why I have 2 forks).
For the moment, with the following code, I create one child to execute the command using execvp and then I write the result to a variable. The parent get it and display it. I also introduce an other fork to correctly execute my pipe. It is working (it execute and display the right result), but my child never finished, execvp blocked after executing my second command after the pipe.
With some research I see it's maybe because I don't close one of my file descriptor, but I checked multiple times and I really didn't see the problem or the descriptor not closed here...
Anyone know what I am doing wrong ?
I am not really good in c, forks & pipes have really dark magic power for me, and I don't understand quite well how it is working at the end... So if you have some suggestions or think something I do is not good, feel free to say it in comments.
Here is my function :
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<fcntl.h> // open function
#include<sys/types.h>
#include<sys/stat.h>
#include<getopt.h>
#include<stdbool.h>
#define STDOUT 1
#define STDERR 2
int main(int argc, char** argv)
{
//PARENT
pid_t pid, wpid, pid2;
int status, status2;
const int MIN_BUFF_SIZE = 1024;
int fd[2];
if (pipe(fd) == -1) {
perror("pipe");
}
printf("After creating pipe \n");
pid = fork();
if (pid == 0) {
// First child process (parent of second child)
printf("First child process before fork again\n");
pid2 = fork();
if(pid2 == 0)
{
printf("Second child process begin\n");
//second child we need to execute the left command
close(fd[0]);
printf("Second child |Redirect stdout > stdin\n");
dup2(fd[1], STDOUT_FILENO); // Redirect stdout to stdin
//test data
char* test[3];
test[0] = "ls\0";
test[1] = "-l\0";
test[2] = NULL;
//TODO test to remove
if (execvp(test[0], test) == -1) {
perror("shell launch error : error in child process > execvp failed \n");
exit(errno);
}
printf("Second child | After execvp\n");
exit(errno);
}else if(pid<0)
{
perror("shell launch error : error forking second child");
}else{
do {
wpid = waitpid(pid2, &status2, WUNTRACED);
printf("Second parent\n");
//Parent
close(fd[1]);
printf("Second parent | Redirect stdout > stdin\n");
dup2(fd[0], STDIN_FILENO);
printf("Second parent | After Redirect stdout > stdin\n");
//test data : grep toto
char* test2[3];
test2[0] = "grep\0";
test2[1] = "toto\0";
test2[2] = NULL;
printf("Second parent | Av dup2 fd stdout\n");
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
printf("Second parent | Ap dup2 fd stdout\n");
if (execvp(test2[0], test2) == -1) {
perror("shell launch error : error in child process > execvp failed \n");
exit(errno);
}
exit(errno);
} while (!WIFEXITED(status2) && !WIFSIGNALED(status2));
}
} else if (pid < 0) {
// Error forking
perror("shell launch error : error forking");
} else {
do {
//wait child process to finish it execution. So, according to project requirements,
//we need to print the result of the command after the child process is finished
wpid = waitpid(pid, &status, WUNTRACED);
printf("Finished waiting for %d\n", wpid);
close(fd[1]); // close the write end of the pipe in the parent
if(status != 0)
{
printf("Status : %d\n", status);
}else{
printf("We are in the first parent \n");
}
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
printf("Finish ! \n");
return 0;
}
And here is the output after execution :
After creating pipe
First child process before fork again
Second child process begin
Second child |Redirect stdout > stdin
Second parent
Second parent | Redirect stdout > stdin
Second parent | After Redirect stdout > stdin
Second parent | Av dup2 fd stdout
Second parent | Ap dup2 fd stdout
-rw-r--r-- 1 ubuntu ubuntu 0 Mar 8 08:20 toto.txt
(and here it does not come back to my shell, it is waiting... something...)
I see a lot of subject about Implementing pipe in c and Connecting n commands with pipes in a shell? but they don't have 2 fork like me, and it's my problem here.
Edit
I edit my post with @William and @Toby suggestions (closing my descriptor with the pattern given in comments and closing write end of the pipe in the grandparent process before waiting children). I added a comment //new for all new line I added to help other people with the same problem to see the changes. My program is not blocked by execvp anymore.
I always have a problem, if in my grandparent process I tried to read stdout, they are nothing in it instead of my command, I missed one redirection or I closed to much descriptor this time ?
//PARENT
pid_t pid, wpid, pid2;
int status, status2;
const int MIN_BUFF_SIZE = 1024;
int fd[2];
if (pipe(fd) == -1) {
perror("pipe");
}
printf("After creating pipe \n");
pid = fork();
if (pid == 0) {
// First child process (parent of second child)
printf("First child process before fork again\n");
pid2 = fork();
if(pid2 == 0)
{
printf("Second child process begin\n");
//second child we need to execute the left command
close(fd[0]);
printf("Second child |Redirect stdout > stdin\n");
dup2(fd[1], STDOUT_FILENO); // Redirect stdout to stdin
close(fd[1]); // NEW
//test data
char* test[3];
test[0] = "ls\0";
test[1] = "-l\0";
test[2] = NULL;
//TODO test to remove
if (execvp(test[0], test) == -1) {
perror("shell launch error : error in child process > execvp failed \n");
exit(errno);
}
printf("Second child | After execvp\n");
exit(errno);
}else if(pid<0)
{
perror("shell launch error : error forking second child");
}else{
do {
wpid = waitpid(pid2, &status2, WUNTRACED);
printf("Second parent\n");
//Parent
close(fd[1]);
printf("Second parent | Redirect stdout > stdin\n");
dup2(fd[0], STDIN_FILENO);
close(fd[0]); // NEW
printf("Second parent | After Redirect stdout > stdin\n");
//test data : grep toto
char* test2[3];
test2[0] = "grep\0";
test2[1] = "toto\0";
test2[2] = NULL;
printf("Second parent | Av dup2 fd stdout\n");
close(fd[0]); // NEW
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
printf("Second parent | Ap dup2 fd stdout\n");
if (execvp(test2[0], test2) == -1) {
perror("shell launch error : error in child process > execvp failed \n");
exit(errno);
}
exit(errno);
} while (!WIFEXITED(status2) && !WIFSIGNALED(status2));
}
} else if (pid < 0) {
// Error forking
perror("shell launch error : error forking");
} else {
do {
close(fd[1]); //NEW close the write end of the pipe in the parent
//wait child process to finish it execution. So, according to project requirements,
//we need to print the result of the command after the child process is finished
wpid = waitpid(pid, &status, WUNTRACED);
printf("Finished waiting for %d\n", wpid);
char* line_to_display = malloc(1);
line_to_display = '\0';
if(status != 0)
{
printf("Status : %d\n", status);
}else{
printf("We are in the first parent \n");
ssize_t bytes_read = 1;
do {
line_to_display = realloc(line_to_display, 1024);
//sizeof(char) = 1 so don't need to do MIN_BUFF_SIZE * sizeof(char)
bytes_read = read(fd[0], line_to_display, 1024);
} while (bytes_read > 0);
printf("%s\n", line_to_display);
}
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
printf("Finish ! \n");
return 0;
}
main
definition. – Arndt Jonassonvoid main()
is wrong — you're using Unix calls, so you can't even claim the 'Windows exception'. See [What shouldmain()
return in C and C++?]( stackoverflow.com/questions/204476) for the details. – Jonathan Lefflerdup2()
one end of a pipe to standard input or standard output, close both of the original file descriptors frompipe()
as soon as possible. In particular, that means before using any of theexec*()
family of functions. The rule also applies with eitherdup()
orfcntl()
withF_DUPFD
. – Jonathan Lefflerclose(fd[0); dup2(fd[1], STDOUT_FILENO); close(fd[1]);
. You need to close the end of the pipe after youdup
it. – William Pursell