main.cpp
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <fcntl.h>
using namespace std;
#include "shell_lib.h"
int main()
{
string user_input = "";
do
{
/*Display prompt and read a line*/
print_prompt();
getline(cin,user_input);
/*handling pipe line*/
vector<string> command_list = split_string(user_input,"|");
int size = (int)command_list.size();
if (size==0)//filter check: The user just press enter with no command
continue;
else if (size==1)//handling command without pipe
{
/*Tokenize the command*/
vector<string> tokenized_user_input = tokenize(command_list[0]);
/*Handle wildcards*/
handle_wildcards(tokenized_user_input);
if (tokenized_user_input.size()==0)//filter check: The user just press enter with no command
continue;
/*Handle built-in command and special command like shell exit*/
if (handle_built_in_command(tokenized_user_input))
continue;
if (exit_shell_cmd(tokenized_user_input))
exit(0);//exit the shell process in a normal way
/*Execute the command*/
int command_exit_status = execute_command_core(tokenized_user_input);
/*Check the exit status and report*/
check_and_report_failure(command_exit_status);
}
else if (size>1)//indicates command containing at least one pipe and two seperate command
{
int fd_read_from_previous=0;//saves the file descriptor in previous command
for (int command_index=0;command_index<size;command_index++)//for every single command
{
/*Tokenize the command*/
vector<string> tokenized_user_input = tokenize(command_list[command_index]);
if (tokenized_user_input.size()==0)//meaning it is an empty command
continue;
/*Handle wildcards*/
handle_wildcards(tokenized_user_input);
/*Main process is the manager handling pipe, all the command will be handled in child process one after another*/
int pipefd[2];
if (pipe(pipefd) == -1)
{
cerr<<"pipe create error!"<<endl;
exit(EXIT_FAILURE);
}
int child_pid;
int child_status = -1;
child_pid = fork();
if (child_pid==-1)//fork fails
{
cerr<<"Fail to fork!"<<endl;
exit(EXIT_FAILURE);
}
else if (child_pid==0) //meaning it is the child process
{
vector<string> clean_command;//saves commands which erase the I/O redirection parts
handle_io_redirection(tokenized_user_input,clean_command);
/*Handling pipe,needed to be done after io_redirection handled to overwrite io_redirection.
It is different when:
1. it is the first command in a command list
2. it is the last command in a command list
3. other command in the command list
*/
if (command_index==0)//meaning it is the first command in a command list, redirect output to pipe
{
dup2(pipefd[1],1);
fd_read_from_previous=pipefd[1];
}
else if (command_index==size-1)//meaning it is the last command in a command list, redirect input to fd_read_from_previous
{
dup2(fd_read_from_previous,0);
}
else//redirect input to fd_read_from_previous and output to pipe
{
dup2(fd_read_from_previous,0);
dup2(pipefd[1],1);
fd_read_from_previous=pipefd[1];
}
/*Handle built-in command and special command like shell exit*/
if (handle_built_in_command(clean_command))
continue;
if (exit_shell_cmd(clean_command))
exit(0);//exit the shell process in a normal way
vector<char *> execvp_argument;
generate_argument(clean_command,execvp_argument);
execvp(clean_command[0].c_str(),&execvp_argument[0]);
cerr<<"Command not found!"<<endl;//should not execute this line unless execvp fails
exit(EXIT_FAILURE);
}
else//meaning it is the parent process
{
child_pid = wait(&child_status);//the parent process wait the child process to finish and fetch the exit_status
/*Check the exit status and report*/
check_and_report_failure(child_status);
//if the previous process end it will close its holding fds, if the parent closes them again, pipefd may not be used in next command
//so we comment this two lines.
//close(pipefd[0]);//Close unused read end
//close(pipefd[1]);//Close unused write end
}
}
}
else
{
cerr<<"Command list size<0!"<<size<<endl;//The program should never execute to here
}
}
while (true);
return 0;
}
shell_lib.h
#ifndef SHELL_LIB_H
#define SHELL_LIB_H
#include <iostream>
#include <string>
#include <vector>
#include <glob.h>
using namespace std;
/*Functions used by main function*/
//=========================================
void print_prompt();
/*Tokenized user_input by white space*/
vector<string> tokenize(string user_input);
/*Handle wildcards and make changes on tokenized_user_input*/
void handle_wildcards(vector<string> & tokenized_user_input);
/*
Input: user_input
Determine if user_input indicate exiting the shell
If so, return true, otherwise return false
*/
bool exit_shell_cmd(const vector<string> & handled_user_input);
/*
Execute the command using tokenized command
Return command execution's return value.
*/
int execute_command_core(const vector<string> & tokenized_command);
/*
Check command_exit_status and report according to the command_exit_status if encounter with failure
Reference to http://tldp.org/LDP/abs/html/exitcodes.html
*/
void check_and_report_failure(int command_exit_status);
/*
If tokenized_user_input is a built-in command, handle it and return true
Otherwise return false
*/
bool handle_built_in_command(const vector<string> & tokenized_user_input);
//=========================================
/*Function library*/
//=========================================
/*
split the str with delimiter, return the result in vector<string>
*/
vector<string> split_string(const string& str,const string& delimiter);
/*
Generate the argument command char* array used by system call execvp, which must end with NULL element
*/
void generate_argument(const vector<string> & tokenized_command,vector<char *> & execvp_argument);
/*
Two tasks for this function:
1. Handle the I/O redirection using dup2 to manipulate the file descriptors
2. Generate a clean_command to be executed which erase the i/O redirection tokens
*/
void handle_io_redirection(const vector<string> & tokenized_command,vector<string> & clean_command);
/*
These two functions do the similar things:
1. Determine if the token is an I/O redirection token
2. If it is, manipulate the file descriptor accordingly and return true, otherwise return false
*/
bool handle_input_redirection(const string & token);
bool handle_output_redirection(const string & token);
/*function used to handle wildcards*/
inline std::vector<std::string> glob(const std::string& pat)
{
glob_t glob_result;
glob(pat.c_str(),GLOB_TILDE,NULL,&glob_result);
vector<string> ret;
for(unsigned int i=0;i<glob_result.gl_pathc;++i)
{
ret.push_back(string(glob_result.gl_pathv[i]));
}
globfree(&glob_result);
return ret;
}
//=========================================
#endif
shell_lib.cpp
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <fcntl.h>
using namespace std;
#include "shell_lib.h"
/*Functions used by main function*/
//=========================================
void print_prompt()
{
cout<<"psh>";
}
/*Tokenized user_input by white space*/
vector<string> tokenize(string user_input)
{
return (split_string(user_input," "));
}
/*Handle wildcards and make changes on tokenized_user_input*/
void handle_wildcards(vector<string> & tokenized_user_input)
{
/*
std::vector<string>::iterator iter = tokenized_user_input.begin();
while (iter!=tokenized_user_input.end())
{
int str_length = (int)(*iter).length();
if (str_length==0)
continue;
Do not handle wildcards in "..." and '...'
char first_char = tokenized_user_input[i][0];
char last_char = tokenized_user_input[i][str_length-1];
if ((first_char=='"' && last_char=='"') || (first_char=='\'' && last_char=='\''))
continue;
vector<string> wildcard_result = glob((*iter));
if (wildcard_result.size()==1)
*iter=wildcard_result[0];
else if (wildcard_result.size()>1)
{
?
*iter=wildcard_result[0];
iter = tokenized_user_input.insert(iter,wildcard_result.begin()+1,wildcard_result.end());
iter+=(wildcard_result.size*()-1);
continue;
}
iter++;
}
*/
}
/*
Input: user_input
Determine if user_input indicate exiting the shell
If so, return true, otherwise return false
*/
bool exit_shell_cmd(const vector<string> & handled_user_input)
{
if (handled_user_input.size()==1 && handled_user_input[0]=="exit")
return true;
else
return false;
}
/*
Execute the command using tokenized command
Return command execution's return value.
*/
int execute_command_core(const vector<string> & tokenized_command)
{
if (tokenized_command.size()==0)
return 0;//empty command treated as success
int pid;
int status;
pid = fork();
if (pid==0) //meaning it is the child process
{
vector<string> clean_command;//saves commands which erase the I/O redirection parts
handle_io_redirection(tokenized_command,clean_command);
vector<char *> execvp_argument;
generate_argument(clean_command,execvp_argument);
execvp(clean_command[0].c_str(),&execvp_argument[0]);
cerr<<"Command not found!"<<endl;//should not execute this line unless execvp fails
exit(EXIT_FAILURE);
}
pid = wait(&status);//the parent process wait the child process to finish and fetch the exit_status
return status;
}
/*
Check command_exit_status and report according to the command_exit_status if encounter with failure
Reference to http://tldp.org/LDP/abs/html/exitcodes.html
*/
void check_and_report_failure(int command_exit_status)
{
if (command_exit_status<0 || command_exit_status>255)
{
//cerr<<"Exit status out of range [0,255]: "<<command_exit_status<<endl;//should never happen. In practice, it happens a lot...
return;
}
if (command_exit_status>128 && command_exit_status!=130)
{
cerr<<"Fatal error signal: "<<(command_exit_status-128)<<endl;
return;
}
switch (command_exit_status)
{
case 0:
{
break;//succeed, display nothing
}
case 1:
{
cerr<<"Catchall for general errors"<<endl;
break;
}
case 2:
{
cerr<<"Misuse of shell builtins (according to Bash documentation)"<<endl;
break;
}
case 126:
{
cerr<<"Command invoked cannot execute"<<endl;
break;
}
case 127:
{
cerr<<"Command not found!"<<endl;
break;
}
case 128:
{
cerr<<"Invalid argument to exit!"<<endl;
break;
}
case 130:
{
cerr<<"Script terminated by Control-C"<<endl;
break;
}
default:
{
cerr<<"Fail to execute command where command_exit_status is "<<command_exit_status<<endl;
}
}
}
/*
If tokenized_user_input is a built-in command, handle it and return true
Otherwise return false
*/
bool handle_built_in_command(const vector<string> & tokenized_user_input)
{
int size = (int)tokenized_user_input.size();
/*Handle empty command*/
if (size<1)
return true;
/*Handle built-in command*/
if (tokenized_user_input[0]=="cd")
{
if (size==1)//will add support to "cd" command in next iteration
{
cout<<"cd should let you go home.Not supported in this iteration yet. We are sorry. >_<"<<endl;
return true;
}
else
{
if (chdir(tokenized_user_input[1].c_str())!=0)//change current directory fails will return -1
{
cout<<"cd: "<<tokenized_user_input[1]<<": No such file or directory"<<endl;
}
return true;
}
}
else if (tokenized_user_input[0]=="echo")
{
if (size==1)
{
cout<<endl;//imitate what shell do
}
if (size>=2)
{
cout<<tokenized_user_input[1];
for (int i=2;i<size;i++)
{
cout<<" "<<tokenized_user_input[i];
}
cout<<endl;
}
return true;
}
return false;
}
//=========================================
/*Function library*/
//=========================================
/*
split the str with delimiter, return the result in vector<string>
*/
vector<string> split_string(const string& str,const string& delimiter)
{
vector<string> result;
string::size_type pos1 = str.find_first_not_of(delimiter,0);
string::size_type pos2 = str.find_first_of(delimiter,pos1);
while (!(pos1==string::npos && pos2==string::npos))//
{
result.push_back(str.substr(pos1,pos2-pos1));
pos1 = str.find_first_not_of(delimiter,pos2);
pos2 = str.find_first_of(delimiter,pos1);
}
return result;
}
/*
Generate the argument command char* array used by system call execvp, which must end with NULL element
*/
void generate_argument(const vector<string> & tokenized_command,vector<char *> & execvp_argument)
{
execvp_argument.clear();
int size = (int)tokenized_command.size();//to avoid warning
for (int i=0;i<size;i++)
{
execvp_argument.push_back(const_cast<char *>(tokenized_command[i].c_str()));
}
execvp_argument.push_back(NULL);
}
/*
Two tasks for this function:
1. Handle the I/O redirection using dup2 to manipulate the file descriptors
2. Generate a clean_command to be executed which erase the i/O redirection tokens
PS:
Why we don't make change on tokenized_command?
Because we think the execute_command's meaning should not change it, if commands changed after execution it will get people confused.
And the cost is anthor vector<string> which I know can be saved.
*/
void handle_io_redirection(const vector<string> & tokenized_command,vector<string> & clean_command)
{
clean_command.clear();
int size = (int)tokenized_command.size();
for (int i=0;i<size;i++)
{
if (!handle_input_redirection(tokenized_command[i]) && !handle_output_redirection(tokenized_command[i]))
{
clean_command.push_back(tokenized_command[i]);
}
}
}
/*
These two functions do the similar things:
1. Determine if the token is an I/O redirection token
2. If it is, manipulate the file descriptor accordingly and return true, otherwise return false
*/
bool handle_input_redirection(const string & token)
{
if (token.length()==0)
return false;
if (token[0]=='<')
{
/*We will treat the rest of the token string as file path*/
string file_path = token.substr(1);
int fd_in = open(file_path.c_str(),O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (fd_in > 0)
{
/*redirect STDOUT for this process*/
dup2(fd_in,0);
}
return true;
}
else
{
return false;
}
}
bool handle_output_redirection(const string & token)
{
if (token.length()==0)
return false;
if (token[0]=='>')
{
/*We will treat the rest of the token string as file path*/
string file_path = token.substr(1);
int fd_out = open(file_path.c_str(),O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (fd_out > 0)
{
/*redirect STDOUT for this process*/
dup2(fd_out,1);
}
return true;
}
else
{
return false;
}
}
//=========================================