Search code examples
cshellpipeforkpipeline

How to chain multiple pipes? (plus weird buggs)


I'm trying to write a simple shell that can handle pipe commands. I want to be able to handle multiple pipes all chained together but I'm having a hard time figuring out how to implement something like this.

This is my current attempt:

int status;
    int lastToken = 0;
    int pipe_pid;

    //create the pipes
    int pipefd[pipes][2];

    // Loop to run all commands in the vertical list.
    while(1){
        if (c->type == TOKEN_PIPE){

        // Here is where we deal with pipes
            for (int i = 0; i < pipes; i++){
                pipe(pipefd[i]);

                pipe_pid = fork();

                //this is a receiving pipe
                if (pipe_pid == 0){
                    // create the write end of the pipe
                    dup2(pipefd[i][WRITE_SIDE], STDOUT_FILENO);
                    close(pipefd[i][READ_SIDE]);
                    close(pipefd[i][WRITE_SIDE]);
                    execvp(c->argv[0], c->argv);
                    // printf("parent pipe\n");
                }
                //this is a writing pipe
                else{
                    close(pipefd[i][WRITE_SIDE]);
                    dup2(pipefd[i][READ_SIDE], STDIN_FILENO);
                    close(pipefd[i][READ_SIDE]);
                    // printf("child pipe\n");
                }
            }



        // This stuff happens for all commands
        lastToken = c->type;
        // If it's the last command, we're done
        if (c->next == NULL){
            break;
        }
        else{
            c = c->next;
        }
    }

the commands are chained together in a linked list, c is my command pointer

pipes is a variable that I create as I parse the in-string, so I know how many '|' I saw in the command. This should tell me the number of child processes I need to fork.

I use pipes to create a 2d array for the pipe descriptors.

Then I want to loop over the pipes and fork once for each, and use dup2 to map the inputs and outputs.

I'm getting inconsistent errors that I can't figure out. First of all, every time I run a pipe command, my shell immediately crashes with no segfault or other printed errors.

Second, if I run commands like echo foo | wc -c I sometimes get 4 and sometimes get 0 as the output.

I'm sure I'm just doing something dumb but I'm not sure what :/


Solution

  • I figured out what I was doing wrong, I was closing the pipes before all the threads were finished using them. I fixed it by pulling out the close calls.

    // writing side of the pipe
    if (c->type == TOKEN_PIPE){
        close(c->pipefd[READ_SIDE]);
        dup2(c->pipefd[WRITE_SIDE], STDOUT_FILENO);
    
    }
    // receiving side of the pipe
    if (commandPrev->type == TOKEN_PIPE){
        close(commandPrev->pipefd[WRITE_SIDE]);
        dup2(commandPrev->pipefd[READ_SIDE], STDIN_FILENO);
    
    }
    

    And then in the parent thread, right before I reep my zombies, I check for pipes that are finished being used and close them.

        // writing side of the pipe
        if (c->type == TOKEN_PIPE){
            close(c->pipefd[READ_SIDE]);
            dup2(c->pipefd[WRITE_SIDE], STDOUT_FILENO);
        }
        // receiving side of the pipe
        if (commandPrev->type == TOKEN_PIPE){
            close(commandPrev->pipefd[WRITE_SIDE]);
            dup2(commandPrev->pipefd[READ_SIDE], STDIN_FILENO);
        close(commandPrev->pipefd[READ_SIDE]);
    

    I'm not sure if this is the optimal way to do it but it works without errors for me.