post #1 of 1
Thread Starter 
Hey all. For my operating systems class, we're creating a program that'll somewhat mimic piping in a terminal. I've been wrestling with it for a while, and as of yesterday neither my TA nor the professor have been of any help. It's due tomorrow, (Sunday) night at 11:50pm. frown.gif

Anyway, I'm going to write out the steps the program takes, and then the relevant portions of code. At the bottom of the post I'll try to make it clear what the behavior I'm seeing is. Hopefully one of you brilliant minds can help me out.

The program starts by asking the user for the processes it needs to run; e.g. "ls | grep ^d | wc -l".
I parse that string and split it to be handled separately.
Once I have the number of commands, I create all the necessary pipes; i.e. for the above example, I make two pipes. I have two global integer arrays, pipeRead and pipeWrite, that hold the file descriptors (FDs) for each end of the pipe.
Then it starts a loop to create each individual process and connect it to the appropriate pipe(s). That's where the following comes in to play; it is called for every run of the loop:
Create_command_process() (Click to show)
Code:
void create_command_process (char cmds[MAX_CMD_LENGTH],   // Command line to be processed
                     int cmd_pids[MAX_CMDS_NUM],          // PIDs of preceding pipeline processes
                                                          // Insert PID of new command processs
                             int i)                     // commmand line number being processed
{
        pid_t childpid;
        int pipefd[2];
        
        char cmd[MAX_CMD_LENGTH];
        char * argvector[MAX_CMD_LENGTH];
        parse_command(cmds, cmd, argvector);
        
        if ((childpid = fork()) == -1) {
                killPipeline(SIGINT);
        }
        
        //here, pipeRead and pipeWrite hold the FDs for each end of the previously created pipes
        if (i < num_cmds - 1) {
                pipefd[0] = pipeRead[i];
                pipefd[1] = pipeWrite[i];
        }
        
        if (childpid > 0) { //piper
                cmd_pids[i] = childpid;
                if (i == num_cmds - 1) {
                        int j;
                        //once the last process has been forked, close all pipe FDs in piper
                        for (j = 0; j < num_cmds - 1; j++) {
                                close(pipeRead[j]);
                                close(pipeWrite[j]);
                        }
                }
        }
        else { //child
                if (i == 0) { //for first command, only set up pipe for writing
                        int out = pipeWrite[i];
                        if (num_cmds > 1) { //only redirect output if there is another command waiting
                                if(dup2(out, STDOUT_FILENO) == -1) {
                                        perror("Failed to redirect stdout of child");
                                        exit(-1);
                                }
                        }
                        close(pipeRead[i]);
                        close(out);
                        
                        execvp(argvector[0], argvector);
                        printf("Child %d failed to execute\n", i);
                        exit(-1);
                }
                else if (i < num_cmds - 1) { //handle pipes on both sides for intermediate commands
                        
                        int in = pipeRead[i-1];
                        int out = pipeWrite[i];
                        
                        if(dup2(out, STDOUT_FILENO) == -1) {
                                perror("Failed to redirect stdout of child");
                                exit(-1);
                        }
                        
                        if(dup2(in, STDIN_FILENO) == -1) {
                                perror("Failed to redirect stdin of child");
                                exit(-1);
                        }
                        
                        close(out);
                        close(in);
                        close(pipeRead[i]);
                        close(pipeWrite[i-1]);
                        
                        execvp(argvector[0], argvector);
                        printf("Child %d failed to execute\n", i);
                        exit(-1);
                }
                else if (i == num_cmds - 1) { //only connect read to previous pipe for last command

                        int in = pipeRead[i-1];
                        
                        if(dup2(in, STDIN_FILENO) == -1) {
                                perror("Failed to redirect stdin of child");
                                exit(-1);
                        }
                        
                        close(pipeWrite[i - 1]);
                        close(in);
                        
                        execvp(argvector[0], argvector);
                        printf("Child %d failed to execute\n", i);
                        exit(-1);
                }
        }
}

Once that all runs, the program calls waitPipelineTermination() that does just that. It calls waitpid() on each of the child processes so they completely close, and log it to a file.
waitPipelineTermination() (Click to show)
Code:
void waitPipelineTermination () {
        
        sleep(1);
        int status;
        int i;
        
        for (i = 0; i < num_cmds; i++) {
                pid_t pid = cmd_pids[i];
                fprintf(logfp, "waiting...");
                waitpid(pid, &status, 0);
                fprintf(logfp, "Process id %d finished\n", pid);
                if (WIFEXITED(status)){
                        cmd_status[i] = WEXITSTATUS(status);
                        fprintf(logfp, "Process id %d finished with exit status %d\n", pid, cmd_status[i]);
                }
                else if (!WIFEXITED(status)) {
                        fprintf(logfp, "Process id %d error'd with exit status %d\n", pid, cmd_status[i]);
                }
        }
}

A few minutes ago, I finally got it to a point where it can run two commands appropriately. I can do something like "ls | wc -l", or even "cat | cat", and it'll work. It needs to handle up to 8 processes, so right now I'm trying to get it to handle a process in the middle; e.g. "ls | wc | cat -n" for some simple output. However, in that case, it just hangs during the "waitpid()"; I've verified it gets to that point by putting the WNOHANG flag, and it returns at that point.

I had a similar issue when I was only dealing with two commands; e.g. if I ran "ls | wc -l", it wouldn't output from wc, just hang. I found out that's because the file descriptors were still open in the parent, and not all the correct ones were closed in the process running "wc -l", and that fixed it. But the same approach doesn't seem to be working for the intermediary commands, even with closing the four file descriptors in the middle chunk of code.

Any help would be appreciated.

EDIT: I'm dealing with other problems now, but if I remember correctly, the above problem was another problem of open file descriptors in the parent. The trick is to close the FDs in the parent process belonging to the pipe for the previous command. That'll allow EOF to go through.
Edited by EfemaN - 3/31/13 at 9:19pm