views:

1386

answers:

9

Hi guys,

For the past few days I have been attempting to write my own shell implementation but I seem to have gotten stuck on getting pipes to work properly. I am able to parse a line and fork off the commands between the pipes (ex: ls | sort) individually but can't seem to get them to pipe input from one into the other.

I think I just don't understand how to use dup2() and pipes properly.

I've now included my code which is still failing... :( So stuck...

void forkAndExecute( char* arrayOfWords[] , vector<pid_t> *vectorOfPIDs , bool hasNextCmd , bool hasPrevCmd) {

int fd[ 2 ];
pid_t pid;

if( hasNextCmd ){
 pipe(fd);
}

pid = fork();

//error if PID < 0
if( pid < 0 ) {
 cerr << ">>> fork failed >>>" << endl;
 exit(-1);
}
//child process if PID == 0
else if( pid == 0 ) {
 if ( hasPrevCmd ){
  dup2(fd[0] , 0);
  close(fd[0]);
  close(fd[1]);

 }
 if ( hasNextCmd ){
  dup2(fd[1],1);
  close(fd[0]);
  close(fd[1]);
 }
 execvp( arrayOfWords[0] , arrayOfWords );
 cout << ">>> command not found >>>" << endl;
 //if logic reaches here, exec failed
 exit(0);
} 
//parent process
else{
 close(fd[0]);
 close(fd[1]);
 //if( ! isLastCmd ){

 //}
 vectorOfPIDs->push_back(pid);
}

}

+4  A: 

Here's a tutorial on UNIX pipes, specifically about how to construct piplines in a shell-like architecture:

http://www.cse.ohio-state.edu/~mamrak/CIS762/pipes%5Flab%5Fnotes.html

Not much fully-written code, but it describes the concepts pretty well.

You could also download source code for virtually any shell, such as bash, tcsh, zsh, etc.

Bill Karwin
+1  A: 

Try reading the source code of Bash to see how they did it.

RoliSoft
+1  A: 

Here are the notes on pipe from the Systems Programming class I took last semester.

thepocketwade
+1  A: 

When I needed to do a similar shell some years ago, I used the book Practical Unix Programming.

It is really useful for examples on many IPC topics. I still have a copy on my desk that I reference from time to time. For $2 - $9 used, it's a pretty good value for what you get.

For what it's worth, just thought I'd mention it.

JPDecker
+3  A: 

The general process would add error handling to this base process (pseudocode):

pipe(fds)
if (fork() is child) {
  dup2(fds[1], 1)
  close(fds[0])
  close(fds[1])
  exec("ls")
}
if (fork() is child) {
  dup2(fds[0], 0)
  close(fds[0])
  close(fds[1])
  exec("sort")
}
close(fds[0])
close(fds[1])
wait()

Create the pipe first. Then fork the child processes so they inherit it. Remap the file descriptors to 0 (stdin) and 1 (stdout) so the processes read and write the appropriate places. Close any remaining file descriptor you don't want the child processes to see or block on when the work is finished. Exec the actual child processes. Wait for them to finish, and you're done!

JB
It's actually less pseudo than I thought. With semicolons at line ends, spelling "is child" as `==0` and using the appropriate variants of `exec` and `wait`, it compiled as valid C and did the job on the second try. Two `wait` calls are necessary.
JB
firstly, thanks for trying to help. Secondly, I'm still stuck hehe. I am really not sure why its not working.
outsyncof
"Stuck" how? Have you tried the sample code I posted? It really works (converted to C, see my first comment), I checked it after posting. Have you failed applying the concept to your codebase?
JB
the problem is that I have N amount of pipes. So each time I go into the forkAndExecute method I will only fork once. I'm having trouble understanding how to save and use old file descriptors.
outsyncof
JB can you please elaborate on "Two wait calls are necessary" please?
wakingrufus
@wakingrufus two new processes are spawned, so we must wait for both of them to complete. A `wait` call only waits for a single process. In the toy example above, this means we'd need to duplicate the last line for it to work consistently (and pass it some arguments, like NULL.) Would it be more clear if I edited the pseudocode to make it real world C?
JB
+4  A: 

First suggestion: Symbolic constants are better than magic numbers.

const int PIPE_READ = 0;
const int PIPE_WRITE = 1;
int fd[2];
pipe(fd);
// Now you can refer to fd[PIPE_READ] and fd[PIPE_WRITE].

Second suggestion: Take a step back and think about what you're trying to accomplish.

You want to spawn two processes, with the first process's stdout connected to the second process's stdin. Right?

So, in C, this means that you need to take call pipe, pass fd[PIPE_WRITE] to the first child process, which will dup2 it to 1, and pass fd[PIPE_READ] to the second child process, which will dup2 it to 0.

Simply looking at forkAndExecute's prototype shows that it can't do that:

void forkAndExecute( char* arrayOfWords[] , vector *vectorOfPIDs , 
    bool hasNextCmd , bool hasPrevCmd);

It only handles a single command, and from looking at that argument list, unless it resorts to evil global variables, there's no way for it to receive a file descriptor from its PrevCmd or receive a file descriptor from its NextCmd.

Think about how to manage the file descriptors that you need, and redesign forkAndExecute to be able to use these.

Josh Kelley
I've been psyching myself out. I'm going to take your advice to heart... take a step back and do this step by step without overwhelming myself.
outsyncof
A: 

You are connecting each program's input to its own output. You probably wanted to connect each program's output to the next one's input instead.

Instead of going for the general case of n processes in a pipeline, you should start with a basis of two and expand from there. You'll gain a better understanding of the way the file descriptors are plugged into each other if you proceed by extending working code instead of shooting directly for the complex structure.

JB
you can always deal with multiple pipes with recursion i think, once the method for one pipe is complete.
wakingrufus
It's not obvious to me how you'd implement this recursively, but why not. What makes it somewhat tricky is that we can't spawn either process of a `pr|pr` schema until the pipe itself is created. In a multiple pipes setup, this extends to: we can't spawn any process until both its input and output pipes are created.
JB
A: 

well, I don't have an answer, but I am working on the same problem atm. I will share what i have. It works for the two commands, but after it is done running, i/o are broken. in a strange way i haven't been able to figure out yet. call the plumber!

void pipeCommand(char** cmd1, char** cmd2) {
  int fds[2]; // file descriptors
  pipe(fds);
  int oldIn, oldOut;
  // child process #1
  if (fork() == 0) {
    // Reassign stdin to fds[0] end of pipe.
    oldIn = dup(STDIN_FILENO);
    dup2(fds[0], STDIN_FILENO);
    close(fds[1]);
    close(fds[0]);
    // Execute the second command.
    execvp(cmd2[0], cmd2);
  // child process #2
  } else if ((fork()) == 0) {
    oldOut = dup(STDOUT_FILENO);
    // Reassign stdout to fds[1] end of pipe.
    dup2(fds[1], STDOUT_FILENO);
    close(fds[0]);
    close(fds[1]);
    // Execute the first command.
    execvp(cmd1[0], cmd1);
  // parent process
  } else
    wait(NULL);
    dup2(oldIn, STDIN_FILENO);
    dup2(oldOut, STDOUT_FILENO);
    close(oldOut);
    close(oldIn);
}

I have a feeling it has to do with what am or am not doing after the wait()

wakingrufus
Your oldIn/oldOut variables are both useless and a probable cause of your woes. You close them in the parent process, but only ever assign them values in the child processes. Your compiler probably defaults them to 0, in effect you're permanently closing stdin (fd 0). Additionnally, you'll need to close the pipe from the parent process before you `wait`; else the reader process might block, waiting for data from the still-open other side.
JB
+2  A: 

ok This is working for me. Hope this helps you:

/************************
function: void pipeCommand(char** cmd1, char** cmd2)
comment: This pipes the output of cmd1 into cmd2.
**************************/
void pipeCommand(char** cmd1, char** cmd2) {
  int fds[2]; // file descriptors
  pipe(fds);
  // child process #1
  if (fork() == 0) {
    // Reassign stdin to fds[0] end of pipe.
    dup2(fds[0], STDIN_FILENO);
    close(fds[1]);
    close(fds[0]);
    // Execute the second command.
    // child process #2
    if (fork() == 0) {
        // Reassign stdout to fds[1] end of pipe.
        dup2(fds[1], STDOUT_FILENO);
        close(fds[0]);
        close(fds[1]);
        // Execute the first command.
        execvp(cmd1[0], cmd1);
    }
    wait(NULL);
    execvp(cmd2[0], cmd2);
    }
    close(fds[1]);
    close(fds[0]);
    wait(NULL);
}
wakingrufus