Indigo Indigo - 8 days ago 6
Bash Question

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 :/

Answer

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.