Help with Forking/stdin/stout/pipes

Hi,

I am trying to write a program that can implement pipes and redirects in a shell environment. Essentially what this means is I have a main process that prompts for user input, and then takes that input and executes it as if it were executed on the command line (for instance, if you type in "ls", it will display what you expect to see). If the user types "exit" the program will exit, and whitespace will cause the program to prompt the user again.

That part of my program is working fine. However, I now must also allow for the user to input redirects and pipes. The code compiles and runs; however, whenever I test for a redirect or pipe it always returns the same error,

terminate called after throwing an instance of 'std::out_of_range'
  what():  basic_string::substr


and does not complete. (for instance, when I type "ls | sort" or "sort < words.txt")

The comments should make it fairly obvious which case corresponds to which user input. I have a function that checks for characters in a string and forks the appropriate routine. The final fork works like a charm.

Apologizes for the eyefull of code, it's really just a lot of forks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#include <iostream>
#include <string>
#include <cstddef>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>

using namespace std;

const int MAX_ARGS = 64;

bool isBlank( string cmd );
bool contains( string cmd, char c );
void split( string str, char c, string &str1, string &str2 );
void parseArgs( string cmd, char * argv[ ] );

int main( )
{
  bool doagain = 1;
  char* input[MAX_ARGS];
  const char* filename;
  int fdarray[2];
  string line,cmd,file,cmd1,cmd2;

  while(doagain){
  begin:
    cout << "Please enter a command or type exit to exit: \n";
    getline(cin,line);
    if (line == "exit" ){
       return 0;       }
    else if(isBlank(line)){
       goto begin;
    }
    else if(contains(line,'<')){ //if command has <, input redirect
       pid_t pid;
       pid = fork();
       if (pid < 0) { //error
          cout << "Fork Failed \n";
       return 1;
       }
       else if (pid == 0) {
           split(line,'<',cmd,file);
           parseArgs(cmd,input);
           //close stdin
           close(0);
           //open file
           open(file.c_str(),O_RDONLY);

           execvp(input[0], input);
           cout << "Error, execvp failed. \n";
           exit(0);
       }
       else {
          wait (NULL);
       }
    }

    else if(contains(line,'>')){ //if command has >, output redirect
    pid_t pid;
    pid = fork();
    if (pid < 0) { //error
          cout << "Fork Failed \n";
       return 1;
       }
       else if (pid == 0) {
           split(line,'>',cmd,file);
           parseArgs(cmd,input);
           //close stdout
           close(1);
           //create file
           mode_t mode = 0644;
           creat(file.c_str(),mode);
           //execvp
           execvp(input[0], input);
           cout << "Error, execvp failed. \n";
           exit(0);
       }
       else {
          wait (NULL);
       }
    }

    else if(contains(line,'|')){ //if command has |, pipe
       pid_t pid; //fork another process
       pid = fork();
       if (pid < 0) { //error
          cout << "Fork Failed \n";
       return 1;
       }
       else if (pid == 0) {
           //create pipe
           split(line,'|',cmd1,cmd2);
           pipe(fdarray);

           pid_t pid;
           pid = fork();
           if (pid < 0) {
              cout << "Fork Failed \n";
           return 1;
           }
           else if (pid == 0){
           //close pipe output
           close(fdarray[0]);
           //copy pipe input to std out
           dup2(fdarray[1],1);
           //execvp
           parseArgs(cmd1,input);
           execvp(input[0],input);
           }
           else {
           //close pipe input
           close(fdarray[1]);
           //copy pipe output to std in
           dup2(fdarray[0],0);
           //execvp
           parseArgs(cmd2,input);
           execvp(input[0],input);
           }
      }
     else{
        wait(NULL);
     }
 }

  else{
       parseArgs(line, input);
       pid_t pid; //fork another process
       pid = fork();
       if (pid < 0) { //error
          cout << "Fork Failed \n";
       return 1;
       }
       else if (pid == 0) {
           execvp(input[0], input);
           cout << "Error, execvp failed.\n";
           exit(0);
       }
       else {
          wait (NULL);
       }
  }
  }
return 0;
}

bool isBlank( string cmd )
{
  size_t pos = cmd.find_first_not_of( " \t" );
  return pos == string::npos;
}

bool contains( string cmd, char c )
{
  size_t pos = cmd.find( c );
  return pos != string::npos;
}

void split( string str, char c, string &str1, string &str2 )
{
  size_t pos = str.find( c );
  str1 = str.substr( 0, pos );
  pos = str.find_first_not_of( " \t", pos + 1 );
  str2 = str.substr( pos );
}

void parseArgs( string cmd, char * argv[ ] )
{
  int argc = 0;
  size_t start = 0;
  size_t end = 0;
  string arg;

  while( end != string::npos  && argc < MAX_ARGS - 1) {
    start = cmd.find_first_not_of( " \t", end );
    end = cmd.find_first_of( " \t", start );
    arg = cmd.substr( start, end - start );
    argv[argc] = new char[arg.length( ) + 1];
    strcpy(argv[argc], arg.c_str());
    argc++;
 }
  argv[argc] = NULL;
}
Last edited on
Um. Why are you mixing C++ and C strings? I see you use strcpy and char*s, but also std::strings and a few of their member functions.

Your error indicates that it's a substring operation (std::string::substr) that's causing issues. I ran your code through a debugger using the "ls | sort" example and your issue seems to be caused by line 178, which doesn't make total nonsense. What happens if the find_first_not_of operation fails?

-Albatross
As far as I know, the find_first_not_of hasn't failed, and I'm unsure what happens if it does.

The parseArgs function takes in the user input and changes it from a string to a C-string, so it can then be used by execvp. It doesn't really deal with anything other than that and always works for the basic case (commands without redirects or pipes) when called.

The functions after main() were provided with explanations (I did not write them), so I am not entirely sure of the fine lines of how they work. I was thinking many of my errors were syntax errors in my forks for < > and |. Essentially with all the functions I was told "this is what they do, this is what you need to pass in".

For example, I'm not confident in my syntax dealing with stdin and stdout on 44-53 (for <) and 63-78 (for >). Much of this comes from personal inexperience and a large unfamiliarity with many of the functions I am having to use in this program.

Edit:

Oh, found in the notes that the parseArgs function was changed a bit without mention between this code and the previous one. It is now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void parseArgs( string cmd, char * argv[ ] )
{
  int argc = 0;
  size_t start = 0;
  size_t end = 0;
  string arg;

  while( end != string::npos  && argc < MAX_ARGS - 1) {
    start = cmd.find_first_not_of( " \t", end );
    if( start == string::npos )
      break;
    end = cmd.find_first_of( " \t", start );
    arg = cmd.substr( start, end - start );
    argv[argc] = new char[arg.length( ) + 1];
    strcpy(argv[argc], arg.c_str());
    argc++;
  }
  argv[argc] = NULL;
}


Which fixes my piping problem. I'll now try it for redirects.

Edit again: This completely fixes my problem and everything runs perfectly. Thanks for mentioning parseArgs being the problem - I would have never thought to look to see if it had changed, because my instructions were to use the previous version of the code and the new functions.
Last edited on
Topic archived. No new replies allowed.