main.cpp
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <stdlib.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);
/*Tokenize the command*/
vector<string> tokenized_user_input = tokenize(user_input);
if (tokenized_user_input.size()==0)//filter check: The user just press enter with no command
continue;
/*Handle directives*/
handle_directives(tokenized_user_input);
/*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);
}
while (true);
return 0;
}
shell_lib.h
#ifndef SHELL_LIB_H
#define SHELL_LIB_H
#include <iostream>
#include <string>
#include <vector>
using namespace std;
/*Functions used by main function*/
//=========================================
void print_prompt();
/*Tokenized user_input by white space*/
vector<string> tokenize(string user_input);
/*Handle directives and make changes on handled_user_input*/
void handle_directives(vector<string> & handled_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);
/*
Pre-request:
1.No duplicate I/O redirection, we will only handle the first occurence of >file and <file. "ls >a_file >bfile" will not be supported.
2.No pipe line contained, just the simplest command
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);
//=========================================
#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 directives and make changes on handled_user_input*/
void handle_directives(vector<string> & handled_user_input)
{
//left empty for current exercise
//should use glob() to implement
}
/*
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;
}
/*
Pre-request:
1.No duplicate I/O redirection, we will only handle the first occurence of >file and <file. "ls >a_file >bfile" will not be supported.
2. No pipe line contained, just the simplest command
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;
}
}
//=========================================