/** * @(#)myshell.cc * * Date 01/31/2011 * * @author * * Benjamin Bertka * bbertka@mss.icics.ubc.ca */ #include "myshell.h" #include #include #include #include #include #include //#include /** * @author bbertka * This is a simple implementation of a shell. Functionality limited! * but the CD command is implemented yeah! * */ using namespace std; /** * The MyShell constructor sets included shell items to their default * values at the start of a session. * PRE: None * POST: A shell session is created */ MyShell::MyShell(){ this->argc = 0; //the system argument count this->filename = NULL; //default file input this->argv.push_back("none"); //default argv vector this->cwd = getenv("PWD"); //default current wd this->prevwd = NULL; //default previous wd parsePath(); //set path for this shell } /** * Begins a shell session * A prompt is called and argument vectors are cleared. * PRE: None * POST:A loop continues until user exits or an error occures */ int MyShell::run(){ cout << "Welcome to SimpleShell!" <argv.clear(); this->argc = 0; this->filename =NULL; } cout << "Goodbye!" << endl; return 0; } /** * Displays the terminal prompt and executes instructions that are * repeatedly called when this method is invoked by the run() func * When a User enters a command via getCommand(), if return is true * then a valid command was issued and lookUpPath() searches for * a file. If the file/command is found then an attempt is made * to execute the command using the system call execve(). Returns 0 if user * types "exit" or 1 otherwise * PRE: MyShell class is running * POST: User's input is parsed and possibly executed. * when user inputs "exit" */ int MyShell::prompt(){ cout << "User@simpleShell> "; if (getCommand()){ if (lookUpPath()){ if(executeCommand()){ return 1; }else{ cout << "could not execute command" << endl; return 1; } }else if(argv.size() > 0 && argv[0] == "exit"){ return 0; }else{ cout << "could not find path" << endl; return 1; } } if(argv.size() > 0){ if(argv[0] == "exit"){ return 0; } cout << this->argv[0] << ": " << "command not found" << endl; } return 1; } /** * This C style function executes the User's file with **argv commands * User's path variables. First a conversion from String vectors * must be made since execve() takes char* const* data types, hence * a conversion is made from MyShell's String argv and path * attributes, the old fashioned way. * Once the args ahve been set then a call to fork() is made and * program is executed under a new thread. Return 1 on success, 0 on fail * @param this->filename * Used to store the program to execute * @param this->argv * Arguments from command line * @param this->env * User PATH variable * PRE: filename, argv, and envp are set with valid values * POST: Hopefully the command is found and is executed. */ int MyShell::executeCommand(){ int childStatus = 0; pid_t procID; static char *argVector[100]; //allocate space for **argv static char *pathEnv[100]; //allocate space for **envp this->filename = this->argv[0].c_str(); //set this filename command /*This is where we allocate the memory for the char* const* variables which get passed to execve() */ for(int i = 0; i < this->argv.size(); ++i){ argVector[i] = (char*)malloc(sizeof(char) * (this->argv[i].size() + 1)); memcpy(argVector[i], &(this->argv[i])[0], this->argv[i].size()); } for(int i = 0; i < this->path.size(); ++i){ pathEnv[i] = (char *)malloc(sizeof(char) * (this->path[i].size() + 1)); memcpy(pathEnv[i], &(this->path[i])[0], this->path[i].size()); } this->argv.clear(); //clear out argv for next time procID = fork(); //begin new process if(procID >= 0){ if(procID == 0){ //execute execve(this->filename, argVector, pathEnv); return 1; }else{ wait(&childStatus); //wait /* Here is where some memory clearance occurs. Release memory from the argv and envp variables*/ for(int i=0;argVector[i]!=NULL;++i) { bzero(argVector[i], strlen(argVector[i])+1); argVector[i] = NULL; free(argVector[i]); } for(int i=0;pathEnv[i]!=NULL;++i) { bzero(pathEnv[i], strlen(pathEnv[i])+1); pathEnv[i] = NULL; free(pathEnv[i]); } this->filename = NULL; //cleared too } }else{ cout << "fork failed " << endl; return 0; //return 0 on failure to fork } return 1; } /** * This method parses the User's environment path by sending the PATH * arguement to getenv() function then parses the path string saving * each ':' dilineated substring into a string vector; * PRE: User has a valid PATH set * POST: User PATH is set, returns 1 on completion */ int MyShell::parsePath(){ string PATH = getenv("PATH"); string dir; this->path.clear(); while(PATH.find(":") != string::npos){ dir = PATH.substr(0, PATH.find_first_of(":")); this->path.push_back(dir); PATH = PATH.substr(PATH.find_first_of(":") + 1, PATH.length()); } this->path.push_back(PATH); return 1; } /** * This method checks to see if the user specified command to execute (arg(0)) * is a valid file. The environment PATH is checked for this file for the * case of an explicit path, or the relative path is checked depending on input * This method uses the access() function to see if the file is accessible * PRE: User command is a vali dcommand * POST: A value of 1 is returned if the path to file exists or Zero otherwise */ int MyShell::lookUpPath(){ /* There is some redundancy in this method since searching for the various strings isnt necessary */ string PATH = " "; if( this->argv[0].find("/") != string::npos ){ if(access(argv[0].c_str(), F_OK) == 0){ return 1; } }else if(this->argv[0].substr(0,2).find("..") != string::npos ){ if(access(argv[0].c_str(), F_OK) == 0){ return 1; } }else if(this->argv[0].find(".") != string::npos){ if(access(argv[0].c_str(), F_OK) == 0){ return 1; } }else{ for(int i = 0; i < this->path.size(); ++i){ string search = this->path[i] + "/" + argv[0]; if(access(search.c_str(), F_OK) == 0){ this->argv[0] = search; return 1; } } } return 0; } /* * This function does not check to see if the change-to directory exists * yet makes a decent effort at implementing the '~' '/' '-' options for cd. * The '-' option for going back a directory is not fully working yet. * PRE: the directory should exist * POST: User changes directory, return 1 on success, or 0 if fail * @param input * The desired directory and argument for the cd command */ int MyShell::changeDirectory(string input){ if(input.find("cd") != string::npos && input.length() == 2){ this->prevwd = getenv("PWD"); chdir(getenv("HOME")); this->cwd = getenv("HOME"); return 1; //if we have "cd" with some arguement at the end }else if(input.find("cd") != string::npos && input.find_first_of(" ") == 2){ input = input.substr(input.find_first_of("cd ") +2, input.length()); input = trim(input); //if the '~' is entered we go to User's home directory if(input.length() == 1 && input.find_first_of("~") == 0){ this->prevwd = getenv("PWD"); chdir(getenv("HOME")); this->cwd = getenv("HOME"); //if the '-' dash is entered we go back to previous working directory }else if(input.length() == 1 && input.find_first_of("-") == 0){ const char * tmp = this->cwd; this->cwd = this->prevwd; chdir(this->cwd); this->cwd = tmp; //if user enters '..' we move up the durectory structure }else if(input.length() == 2 && input.find_first_of("..") == 0){ this->prevwd = this->cwd; chdir(input.c_str()); this->cwd = getenv("PWD"); //if the user enters a '/' slash then we go to root }else if(input.length() == 1 && input.find_first_of("/") == 0){ this->prevwd = this->cwd; chdir(input.c_str()); this->cwd = input.c_str(); }else{ this->prevwd = getenv("PWD"); chdir(input.c_str()); this->cwd = input.c_str(); } return 1; } return 0; } /** * Takes input from the user and saves the command and args if any * before saving input into data structures the input is checked to see * if it is achange directory command: this could probably be done somewhere * else, but for now this works. USer command file name is pushed into * argv vector, along with any args. * PRE: None * POST: On successful input 1 is returned, otherwise if the user used CD * or the command was not accepted a 0 is returned. */ int MyShell::getCommand(){ string input = " "; string arg = " "; ssize_t bad = -1; this->argv.clear(); //clear out the argv if( getline(cin, input) != NULL){ //get input if(changeDirectory(input)){ return 0; //if 'cd' is entered } //get the main command if(input.find(" ") == string::npos && input.length() > 0){ this->argv.push_back(input); ++this->argc; return 1; //get args if they are there }else if(input.length() >= 0 && input[0] != NULL){ while(input.find(" ") != string::npos || input.find("|") != string::npos){ input = trim(input); arg = input.substr(0, input.find_first_of(" ")); arg = trim(arg); this->argv.push_back(arg); ++this->argc; input = input.substr(input.find_first_of(arg) + arg.length(), input.length() ); } return 1; }else{ //cout << "no line" <argc ; ++i){ cout << this->argv[i] << endl; } return 1; }