简体   繁体   中英

How to redirect stdout to stdin using fork and pipe and execvp in c?

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 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;
}

The write end of the pipe is still open in the grandparent process. We can avoid this problem by creating the pipe in the first child, like this:

    pid = fork();

    if (pid == 0) {
        // First child process (parent of second child)

        int fd[2];
        if (pipe(fd) == -1) {
            perror("pipe");
        }
        fprintf(stderr, "After creating pipe \n");

        fprintf(stderr, "First child process before fork again\n");
        pid2 = fork();

We then need to remove the close(fd[1]); that's now out of scope (lean on the compiler).


I note in passing that there's a typo: if(pid<0) instead of pid2<0 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM