diff --git a/.gitignore b/.gitignore index 399650b..c84ce36 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ build-* #internal test -msh-console-library/main.cpp +#msh-console-library/main.cpp # Autosaves *.autosave diff --git a/README.md b/README.md index b87655a..d28906b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,17 @@ -# msh-console -Library that provides a bash-like interface for CLI C++ programs +# Msh-console +* included flag handling implemented with popt.h; +Library that provides a bash-like command line for C++ programs. Features included: +* creation of personalized commands in the same thread of the shell or in other threads; +* basic shell functionalities, such as execution of other programs in the system. This is implemented with execvp(). +What is not actually included: +* advanced shell features, like: + * piping, + * redirection, + * scripting, + * command history. ### Credits -Code based on "Write a Shell in C" - by Stephen Brennan (http://brennan.io/2015/01/16/write-a-shell-in-c/) +Code based on "Write a Shell in C" tutorial by Stephen Brennan (http://brennan.io/2015/01/16/write-a-shell-in-c/). The code has been slightly modified. The code contains stringtoargcargv.cpp, a set of functions written by Bernhard Eder (http://web.archive.org/web/20121030075237/http://bbgen.net/blog/2011/06/string-to-argc-argv) for parsing a string into argc and argv. -### The library +### Compiling The library can be compiled as shared library with the CMakeLists.txt file already in msh-console-library/. diff --git a/msh-console-library/CMakeLists.txt b/msh-console-library/CMakeLists.txt index 483d9aa..7870c9f 100644 --- a/msh-console-library/CMakeLists.txt +++ b/msh-console-library/CMakeLists.txt @@ -1,22 +1,38 @@ -# msh-console dynamic library by praticamentetilde (Claudio Maggioni) -# licensed with "The Unlicense" +# MIT License +# +# Copyright (c) 2016 Claudio Maggioni +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. project(msh-console-library) cmake_minimum_required(VERSION 2.8) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -lpopt") -FILE(GLOB sources *.cpp) -FILE(GLOB headers *.h) +list(APPEND SRC_LIST "command.cpp" "commandexecutor.h" "commands.cpp" "datas.cpp" "shell.cpp" "stringtoargcargv.cpp" + "commandexecutor.cpp" "command.h" "data.cpp" "main.cpp" "shell.h" "stringtoargcargv.h") -set(lib OFF) +set(lib ON) #off=debug demo with main.cpp, on=library if(${lib}) - MESSAGE( STATUS "Building .so.1") - #compile the library - # Create a library which includes the source listed. - # The extension is already found. Any number of sources could be listed here. - list(REMOVE_ITEM SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) + MESSAGE(STATUS "Building .so.1") + list(REMOVE_ITEM SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) #remove demo file add_library (mshconsole SHARED ${SRC_LIST}) else(${lib}) - MESSAGE( STATUS "Building exec (debug only)") - add_executable(${PROJECT_NAME} ${sources} ${headers}) + MESSAGE(STATUS "Building exec (debug only)") + add_executable(${PROJECT_NAME} ${SRC_LIST}) endif(${lib}) diff --git a/msh-console-library/command.cpp b/msh-console-library/command.cpp index 8263af5..d877914 100644 --- a/msh-console-library/command.cpp +++ b/msh-console-library/command.cpp @@ -1,7 +1,31 @@ +/** + MIT License + + Copyright (c) 2016 Claudio Maggioni + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + #include "shell.h" namespace mshconsole { - Command::Command(const string& n, int (*funcptr)(CommandExecutor* whoExecuted,const Datas& data, const vector argv)) : + Command::Command(const std::string& n, int (*funcptr)(CommandExecutor* whoExecuted,const Datas& data, const std::vector argv)) : name(n), funcCommand(funcptr), optionNum(0){ checkObj(); } @@ -16,7 +40,7 @@ namespace mshconsole { optionNum++; } - int Command::execute(const struct Params& p, CommandExecutor* c){ + int Command::execute(const struct Params& p, CommandExecutor* c, std::ostream& errorStream){ if(p.argc<=0) return -1; // options a, b, c take integer arguments @@ -46,32 +70,32 @@ namespace mshconsole { // poptGetNextOpt returns -1 when the final argument has been parsed // otherwise an error occured if (val != -1) { - cerr << name << ": "; + errorStream << name << ": "; switch(val) { case POPT_ERROR_NOARG: - cerr << "argument missing for an option\n"; + errorStream << "argument missing for an option\n"; return 1; case POPT_ERROR_BADOPT: - cerr << "option not found\n"; + errorStream << "option not found\n"; return 1; case POPT_ERROR_BADNUMBER: case POPT_ERROR_OVERFLOW: - cerr << "option could not be converted to number\n"; + errorStream << "option could not be converted to number\n"; return 1; default: - cerr << "unknown error in option processing\n"; + errorStream << "unknown error in option processing\n"; return 1; } } - vector nonOptionArgs; + std::vector nonOptionArgs; while (poptPeekArg(pc) != NULL) nonOptionArgs.push_back(const_cast(poptGetArg(pc))); return (*funcCommand)(c,datas,nonOptionArgs); } - const string& Command::getName(){ + const std::string& Command::getName(){ return this->name; } diff --git a/msh-console-library/command.h b/msh-console-library/command.h index 7d168a2..78c11e7 100644 --- a/msh-console-library/command.h +++ b/msh-console-library/command.h @@ -1,7 +1,32 @@ +/** + MIT License + + Copyright (c) 2016 Claudio Maggioni + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + #ifndef COMMAND_H #define COMMAND_H #pragma once #include +#include #include #include extern "C"{ @@ -9,15 +34,12 @@ extern "C"{ } #include "commandexecutor.h" -using std::string; -using std::vector; - namespace mshconsole{ class Command { - const string name; + const std::string name; void checkObj(); - vector opts; + std::vector opts; static const struct poptOption POPT_TERMINATOR; size_t optionNum; @@ -55,26 +77,26 @@ namespace mshconsole{ double getDouble() const; }; - class Datas : public vector{ + class Datas : public std::vector{ public: - Data* getOptData(const string& optionName) const; + Data* getOptData(const std::string& optionName) const; Data* getOptData(char opt) const; - Data* operator[](const string& opt) const; + Data* operator[](const std::string& opt) const; }; class DuplicatedOptionException{}; class CommandNameNotValidException{}; class InvalidOptionException{}; - Command(const string& n, int (*funcptr)(CommandExecutor* whoExecuted,const Datas& data, const vector argv)); + Command(const std::string& n, int (*funcptr)(CommandExecutor* whoExecuted,const Datas& data, const std::vector argv)); Command(const Command&); ~Command(); - const string& getName(); - int execute(const struct Params&, CommandExecutor*); + const std::string& getName(); + int execute(const struct Params&, CommandExecutor*,std::ostream& errorStream=std::cerr); void addOption(const poptOption& option); private: - int (*funcCommand)(CommandExecutor* whoExecuted,const Datas& data, const vector argv); + int (*funcCommand)(CommandExecutor* whoExecuted,const Datas& data, const std::vector argv); }; } diff --git a/msh-console-library/commandexecutor.cpp b/msh-console-library/commandexecutor.cpp index ad75fe0..8906dd0 100644 --- a/msh-console-library/commandexecutor.cpp +++ b/msh-console-library/commandexecutor.cpp @@ -1,3 +1,27 @@ +/** + MIT License + + Copyright (c) 2016 Claudio Maggioni + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + #include "commandexecutor.h" namespace mshconsole { CommandExecutor::CommandExecutor() {} diff --git a/msh-console-library/commandexecutor.h b/msh-console-library/commandexecutor.h index 7c335a2..17ab514 100644 --- a/msh-console-library/commandexecutor.h +++ b/msh-console-library/commandexecutor.h @@ -1,3 +1,27 @@ +/** + MIT License + + Copyright (c) 2016 Claudio Maggioni + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + #ifndef COMMANDEXECUTOR_H #define COMMANDEXECUTOR_H #pragma once diff --git a/msh-console-library/commands.cpp b/msh-console-library/commands.cpp index 6e6068c..258f3f8 100644 --- a/msh-console-library/commands.cpp +++ b/msh-console-library/commands.cpp @@ -1,3 +1,27 @@ +/** + MIT License + + Copyright (c) 2016 Claudio Maggioni + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + #include "shell.h" namespace mshconsole { Shell::Commands::Commands(Shell* s) : commands(), threadCommands() diff --git a/msh-console-library/data.cpp b/msh-console-library/data.cpp index c830664..2ced1cc 100644 --- a/msh-console-library/data.cpp +++ b/msh-console-library/data.cpp @@ -1,3 +1,27 @@ +/** + MIT License + + Copyright (c) 2016 Claudio Maggioni + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + #include "command.h" namespace mshconsole{ diff --git a/msh-console-library/datas.cpp b/msh-console-library/datas.cpp index 62601a4..5ccbc68 100644 --- a/msh-console-library/datas.cpp +++ b/msh-console-library/datas.cpp @@ -1,7 +1,31 @@ +/** + MIT License + + Copyright (c) 2016 Claudio Maggioni + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + #include "command.h" namespace mshconsole { - Command::Data* Command::Datas::getOptData(const string& opt) const{ + Command::Data* Command::Datas::getOptData(const std::string& opt) const{ for(size_t i=0; isize(); i++){ if(opt == this->at(i)->of->longName) return this->at(i); } @@ -11,7 +35,7 @@ namespace mshconsole { if(opt == this->at(i)->of->shortName) return this->at(i); } } - Command::Data* Command::Datas::operator [](const string& opt) const{ + Command::Data* Command::Datas::operator [](const std::string& opt) const{ if(opt.length()>2) return getOptData(opt); if(opt.length()==1) return getOptData(opt[0]); else return nullptr; diff --git a/msh-console-library/main.cpp b/msh-console-library/main.cpp new file mode 100644 index 0000000..7457a17 --- /dev/null +++ b/msh-console-library/main.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include "../msh-console-library/shell.h" +#include "../msh-console-library/command.h" +using namespace std; +using namespace mshconsole; + +static void setup(Shell *); + +int cdExecute(CommandExecutor* whoExecuted,const Command::Datas& data, const vector argv){ + if (argv.size() == 0) { + cerr << "expected argument to \"cd\"\n"; + } else { + int ret; + if (strcmp("~",argv[0])==0) { + ret = chdir(getenv("HOME")); + } + else { + ret = chdir(argv[0]); + } + if (ret != 0) { + cerr << "cd: error executing the command\n"; + return ret; + } + } + return 0; +} + +int exitExecute(CommandExecutor* whoExecuted,const Command::Datas& data, const vector argv){ + whoExecuted->exit(); + return 0; +} + +int helpExecute(CommandExecutor* whoExecuted,const Command::Datas& data, const vector argv){ + cout << data.getOptData('p')->getString() << "\n"; + if(data.getOptData('h')->getInt()){ + cout << "These are the further details." << endl; + return 0; + } + if(strcmp(data.getOptData('p')->getString(),"prova")==0){ + cout << "prova\n"; + return 0; + } + cout << "Snippet console generated with msh-console library.\nUse option -h for further details." << endl; + return 0; +} + +/** + Little snippet for library use. +*/ + +int main(int argc, char **argv) +{ + string c = "msh-console-test"; + string ps = "[msh-console-test]:"; + Shell mshConsoleTest(c, ps, cin,cout,cerr, &setup); + Command* help = new Command("help", &helpExecute); + struct poptOption h = {"help",'h',POPT_ARG_NONE,NULL,1000,"advance","advaced options"}; + struct poptOption p = {"prova",'p',POPT_ARG_STRING,NULL,1000,"cose","cosecose"}; + help->addOption(h); + help->addOption(p); + + //add builtin commands + mshConsoleTest.addCmd(help); + mshConsoleTest.addCmd(new Command("exit", &exitExecute)); + mshConsoleTest.addCmd(new Command("cd", &cdExecute)); + mshConsoleTest.launch(); + return EXIT_SUCCESS; +} + +void setup(Shell *s){ + cout << "Now entering in test shell...\n" << endl; + //s->executeCmd("stty -ctlecho"); +} diff --git a/msh-console-library/shell.cpp b/msh-console-library/shell.cpp index f988d15..199a124 100644 --- a/msh-console-library/shell.cpp +++ b/msh-console-library/shell.cpp @@ -1,81 +1,109 @@ +/** + MIT License + + Copyright (c) 2016 Claudio Maggioni + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + #include "shell.h" namespace mshconsole { - string Shell::getPs() const - { + const std::string& Shell::getPs() const{ return ps; } - void Shell::setPs(const string &value) - { + void Shell::setPs(const std::string &value){ ps = value; } - string Shell::getName() const - { + const std::string& Shell::getName() const{ return name; } - void Shell::setName(const string &value) - { + void Shell::setName(const std::string &value){ name = value; } - Shell::Shell( string n, string ps,void (*s)(Shell*), void (*pss)(Shell*)) : cmds(this) - { + Shell::Shell(std::string nam, std::string ps, std::istream &i, std::ostream &o, std::ostream &e, + void (*s)(Shell*), void (*pss)(Shell*)) : cmds(this), inputStream(i), + outputStream(o), errorStream(e){ shellSetup = s; - name = n; + name = nam; this->ps = ps; shellPostSetup = pss; - notLoop = false; } int Shell::launch(){ + //Forbid launching shell if it is already launched in other threads. + static bool notLoop=false; + if(notLoop) return -1; + notLoop = true; - //launch setup - if(notLoop) { - return -1; - } - if(shellSetup!=0) { - notLoop = true; - (*shellSetup)(this); - notLoop = false; - } + //Launch the shell setup. + if(shellSetup!=0) (*shellSetup)(this); - //launch loop - string* line; - struct Params p; + //Run the shell loop. int exitCode; + try{ - do { - bool readSuccess; - do{ - cout << ps << " "; + std::string* line=nullptr; + struct Params p; + + while(1){ + delete line; + outputStream << ps << " "; //Print the prompt. + + //Try reading the line. if SIGINT is caught, print another time the prompt and retry. try{ - line = read_line(); - readSuccess = true; + line = readLine(); } catch (IsUndoingLineException){ - cout << "\n"; - readSuccess = false; + outputStream << "\n"; + continue; } - }while(!readSuccess); - p = split_line(line); - executeCmd(p); - } while (1); + + //Try line conversion from string to argc and argv. stringToArgcArgv() throws a std::runtime_error + //exception if the string has unproper quotes. + try{ + p = splitLine(line); + } + catch(std::runtime_error e){ + errorStream << name << ": error in line parsing\n"; + errorStream << e.what() << "\n"; + continue; + } + + //Execute the command. + executeCmd(p); + }; } catch(CommandExecutor::ExitException c) { - exitCode=c.getCode(); + exitCode=c.getCode(); } - //launch postSetup - if(shellPostSetup!=0) { - shellPostSetup(this); - } + //Launch postSetup(). + if(shellPostSetup!=0) shellPostSetup(this); + + notLoop = false; return exitCode; } - int Shell::launchCmd(const struct Params& p) - { - using std::exit; + int Shell::launchCmd(const struct Params& p){ int status; pid_t pid = fork(); @@ -84,21 +112,23 @@ namespace mshconsole { //execute threadCommand int a; + try { a=cmds.launch(p, true); + std::exit(EXIT_SUCCESS); } - catch (CommandNotFoundException){ - //execute bash command or program - if((a=execvp(p.argv[0], p.argv)) == -1) { - cerr << name <<": command " << p.argv[0] << " not found\n"; - } - exit(EXIT_FAILURE); + catch (CommandNotFoundException){} + + //execute bash command or program + if((a=execvp(p.argv[0], p.argv)) == -1) { + outputStream << name << ": execution failed. Errno is set to: "<sa_handler = funcptr; - sa->sa_flags = 0; // not SA_RESTART!; - sigaction(SIGINT, sa, NULL); - delete sa; + struct sigaction* setHandler(unsigned signal, void (*funcptr)(int, siginfo_t*, void*)){ + struct sigaction* before = new struct sigaction(); + struct sigaction sa; + sa.sa_sigaction = funcptr; + sa.sa_flags = 0; + sigaction(signal, &sa, before); } - string* Shell::read_line() - { - string* buffer = new string(); - setEofHandler(EofHandler); - getline(cin,*buffer); // get command - cin.clear(); // clear flags + void restoreHandler(unsigned signal, struct sigaction* before){ + sigaction(signal, before, NULL); + delete before; + } - if(undoingLine){ - undoingLine=false; + std::string* Shell::readLine(){ + std::string* buffer = new std::string(); + struct sigaction* beforeSIGINT = setHandler(SIGINT,SIGINTHandler); + getline(inputStream,*buffer); //Get line of input TODO: handle directional keys + inputStream.clear(); //Clear flags. This is needed for the detection of SIGINT + + if(SIGINTRaised){ + SIGINTRaised=false; throw IsUndoingLineException(); } + + restoreHandler(SIGINT, beforeSIGINT); return buffer; } - struct Params Shell::split_line(const string* line){ + struct Params Shell::splitLine(const std::string* line){ struct Params p; stringToArgcArgv(*line, &(p.argc), &(p.argv)); return p; } - bool Shell::undoingLine = false; + bool Shell::SIGINTRaised = false; void Shell::addCmd(Command* cmd, bool isthread){ cmds.add(cmd, isthread); diff --git a/msh-console-library/shell.h b/msh-console-library/shell.h index de6ef76..85b272e 100644 --- a/msh-console-library/shell.h +++ b/msh-console-library/shell.h @@ -1,3 +1,27 @@ +/** + MIT License + + Copyright (c) 2016 Claudio Maggioni + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + #ifndef SHELL_H #define SHELL_H #pragma once @@ -7,20 +31,14 @@ #include #include #include -#include #include #include +#include #include "command.h" #include #include "commandexecutor.h" #include "stringtoargcargv.h" -using std::string; -using std::cin; -using std::cout; -using std::cerr; -using std::string; -using std::vector; -using std::istringstream; +using namespace StringToArgcArgv; namespace mshconsole { @@ -29,8 +47,8 @@ namespace mshconsole { class Commands { Shell* parent; - vector commands; //commands that work the same thread of the shell - vector threadCommands; //commands that work on a different thread + std::vector commands; //commands that work the same thread of the shell + std::vector threadCommands; //commands that work on a different thread public: Commands(Shell*); @@ -39,40 +57,47 @@ namespace mshconsole { int launch(const struct Params& args, bool launchThread=false); }; - static bool undoingLine; + static bool SIGINTRaised; Commands cmds; - string ps; - string name; + std::string ps; + std::string name; + std::ostream& errorStream; + std::ostream& outputStream; + std::istream& inputStream; void (*shellSetup)(Shell *); void (*shellPostSetup)(Shell *); - bool notLoop; int launchCmd(const struct Params& args); - static void EofHandler(int); - string* read_line(); - struct Params split_line(const string* line); + static void SIGINTHandler(int,siginfo_t *info, void *); + std::string* readLine(); + struct Params splitLine(const std::string* line); class IsUndoingLineException {}; class CommandNotFoundException {}; public: - Shell(string n="msh", string ps="MSH$", void (*s)(Shell*)=0, void (*pss)(Shell*)=0); - string getPs() const; - void setPs(const string &value); - string getName() const; - void setName(const string &value); + Shell(std::string n, std::string ps, std::istream& inputStream, std::ostream& outputStream, + std::ostream& errorStream, void (*s)(Shell*)=0, void (*pss)(Shell*)=0); + const std::string& getPs() const; + void setPs(const std::string &value); + const std::string& getName() const; + void setName(const std::string &value); int launch(); void setShellSetup(void (*)(Shell *)); - void (*getShellSetup())(Shell*); + void (*getShellSetup())(Shell*) const; void setShellPostSetup(void (*)(Shell *)); - void (*getShellPostSetup())(Shell*); + void (*getShellPostSetup())(Shell*) const; //for in-shell commands void addCmd(Command *cmd, bool isThread=false); - size_t howManyCmds() const; + int executeCmd(const struct Params& args); - int executeCmd(const string& args); + int executeCmd(const std::string& args); + size_t howManyCmds() const; }; + + extern struct sigaction* setHandler(unsigned signal, void (*funcptr)(int, siginfo_t*, void*)); + extern void restoreHandler(unsigned signal, struct sigaction* before); } #endif // SHELL_H diff --git a/msh-console-library/stringtoargcargv.cpp b/msh-console-library/stringtoargcargv.cpp index b35d9ab..2e60931 100644 --- a/msh-console-library/stringtoargcargv.cpp +++ b/msh-console-library/stringtoargcargv.cpp @@ -1,4 +1,4 @@ -/* stringtoargcargv.cpp -- Parsing a string to std::vector +/** stringtoargcargv.cpp -- Parsing a string to std::vector Copyright (C) 2011 Bernhard Eder @@ -22,171 +22,176 @@ */ +///The code is modified for the null-termination of argv + #include "stringtoargcargv.h" -/* - * Usage: - * int argc; - * char** argv; - * stringToArgcArgv("foo bar", &argc, &argv); - */ -void stringToArgcArgv(const std::string& str, int* argc, char*** argv) -{ - std::vector args = parse(str); +namespace StringToArgcArgv{ + /* + * Usage: + * int argc; + * char** argv; + * stringToArgcArgv("foo bar", &argc, &argv); + */ + void stringToArgcArgv(const std::string& str, int* argc, char*** argv) + { + std::vector args = parse(str); - *argv = (char**)std::malloc(args.size() * sizeof(char*)); + *argv = (char**)std::malloc((args.size()+1) * sizeof(char*)); - int i=0; - for(std::vector::iterator it = args.begin(); - it != args.end(); - ++it, ++i) - { - std::string arg = *it; - (*argv)[i] = (char*)std::malloc((arg.length()+1) * sizeof(char)); - std::strcpy((*argv)[i], arg.c_str()); - } - - *argc = args.size(); -} - -std::vector parse(const std::string& args) -{ - std::stringstream ain(args); // used to iterate over input string - ain >> std::noskipws; // do not skip white spaces - std::vector oargs; // output list of arguments - - std::stringstream currentArg(""); - currentArg >> std::noskipws; - - // current state - enum State { - InArg, // currently scanning an argument - InArgQuote, // currently scanning an argument (which started with quotes) - OutOfArg // currently not scanning an argument - }; - State currentState = OutOfArg; - - char currentQuoteChar = '\0'; // to distinguish between ' and " quotations - // this allows to use "foo'bar" - - char c; - while(!ain.eof() && (ain >> c)) { // iterate char by char - - if(_isQuote(c)) { - switch(currentState) { - case OutOfArg: - currentArg.str(std::string()); - case InArg: - currentState = InArgQuote; - currentQuoteChar = c; - break; - - case InArgQuote: - if(c == currentQuoteChar) - currentState = InArg; - else - currentArg << c; - break; + int i=0; + for(std::vector::iterator it = args.begin(); + it != args.end(); + ++it, ++i) + { + std::string arg = *it; + (*argv)[i] = (char*)std::malloc((arg.length()+1) * sizeof(char)); + std::strcpy((*argv)[i], arg.c_str()); } + (*argv)[i]=NULL; + *argc = args.size(); } - else if(_isWhitespace(c)) { - switch(currentState) { - case InArg: - oargs.push_back(currentArg.str()); - currentState = OutOfArg; - break; - case InArgQuote: - currentArg << c; - break; - case OutOfArg: - // nothing - break; - } - } - else if(_isEscape(c)) { - switch(currentState) { - case OutOfArg: - currentArg.str(std::string()); - currentState = InArg; - case InArg: - case InArgQuote: - if(ain.eof()) - { -#ifdef WIN32 - // Windows doesn't care about an escape character at the end. - // It just adds \ to the arg. - currentArg << c; -#else - throw(std::runtime_error("Found Escape Character at end of file.")); -#endif + + std::vector parse(const std::string& args) + { + std::stringstream ain(args); // used to iterate over input string + ain >> std::noskipws; // do not skip white spaces + std::vector oargs; // output list of arguments + + std::stringstream currentArg(""); + currentArg >> std::noskipws; + + // current state + enum State { + InArg, // currently scanning an argument + InArgQuote, // currently scanning an argument (which started with quotes) + OutOfArg // currently not scanning an argument + }; + State currentState = OutOfArg; + + char currentQuoteChar = '\0'; // to distinguish between ' and " quotations + // this allows to use "foo'bar" + + char c; + while(!ain.eof() && (ain >> c)) { // iterate char by char + + if(_isQuote(c)) { + switch(currentState) { + case OutOfArg: + currentArg.str(std::string()); + case InArg: + currentState = InArgQuote; + currentQuoteChar = c; + break; + + case InArgQuote: + if(c == currentQuoteChar) + currentState = InArg; + else + currentArg << c; + break; } - else - { -#ifdef WIN32 - // Windows only escapes the " character. - // Every other character is just printed and the \ is added itself. - char c1 = c; - ain >> c; - if(c != '\"') - currentArg << c1; // only ignore \ when next char is " - ain.unget(); -#else - ain >> c; - currentArg << c; -#endif + + } + else if(_isWhitespace(c)) { + switch(currentState) { + case InArg: + oargs.push_back(currentArg.str()); + currentState = OutOfArg; + break; + case InArgQuote: + currentArg << c; + break; + case OutOfArg: + // nothing + break; } - break; + } + else if(_isEscape(c)) { + switch(currentState) { + case OutOfArg: + currentArg.str(std::string()); + currentState = InArg; + case InArg: + case InArgQuote: + if(ain.eof()) + { + #ifdef WIN32 + // Windows doesn't care about an escape character at the end. + // It just adds \ to the arg. + currentArg << c; + #else + throw(std::runtime_error("Found Escape Character at end of file.")); + #endif + } + else + { + #ifdef WIN32 + // Windows only escapes the " character. + // Every other character is just printed and the \ is added itself. + char c1 = c; + ain >> c; + if(c != '\"') + currentArg << c1; // only ignore \ when next char is " + ain.unget(); + #else + ain >> c; + currentArg << c; + #endif + } + break; + } + } + else { + switch(currentState) { + case InArg: + case InArgQuote: + currentArg << c; + break; + + case OutOfArg: + currentArg.str(std::string()); + currentArg << c; + currentState = InArg; + break; + } + } } + + if(currentState == InArg) + oargs.push_back(currentArg.str()); + else if(currentState == InArgQuote) + throw(std::runtime_error("Starting quote has no ending quote.")); + + return oargs; } - else { - switch(currentState) { - case InArg: - case InArgQuote: - currentArg << c; - break; - case OutOfArg: - currentArg.str(std::string()); - currentArg << c; - currentState = InArg; - break; - } + bool _isQuote(char c) + { + if(c == '\"') + return true; + else if(c == '\'') + return true; + + return false; } - } - if(currentState == InArg) - oargs.push_back(currentArg.str()); - else if(currentState == InArgQuote) - throw(std::runtime_error("Starting quote has no ending quote.")); + bool _isEscape(char c) + { + if(c == '\\') + return true; - return oargs; -} - -bool _isQuote(char c) -{ - if(c == '\"') - return true; - else if(c == '\'') - return true; - - return false; -} - -bool _isEscape(char c) -{ - if(c == '\\') - return true; - - return false; -} - -bool _isWhitespace(char c) -{ - if(c == ' ') - return true; - else if(c == '\t') - return true; - - return false; + return false; + } + + bool _isWhitespace(char c) + { + if(c == ' ') + return true; + else if(c == '\t') + return true; + + return false; + } } diff --git a/msh-console-library/stringtoargcargv.h b/msh-console-library/stringtoargcargv.h index b274b9b..1c4d91b 100644 --- a/msh-console-library/stringtoargcargv.h +++ b/msh-console-library/stringtoargcargv.h @@ -1,8 +1,4 @@ -#ifndef STRINGTOARGCARGV_H -#define STRINGTOARGCARGV_H -#pragma once - -/* stringtoargcargv.cpp -- Parsing a string to std::vector +/** stringtoargcargv.cpp -- Parsing a string to std::vector Copyright (C) 2011 Bernhard Eder @@ -26,6 +22,10 @@ */ +#ifndef STRINGTOARGCARGV_H +#define STRINGTOARGCARGV_H +#pragma once + #include #include #include @@ -35,11 +35,12 @@ #include #include -bool _isQuote(char c); -bool _isEscape(char c); -bool _isEscape(char c); -bool _isWhitespace(char c); -std::vector parse(const std::string& args); -void stringToArgcArgv(const std::string& str, int* argc, char*** argv); - +namespace StringToArgcArgv{ + bool _isQuote(char c); + bool _isEscape(char c); + bool _isEscape(char c); + bool _isWhitespace(char c); + std::vector parse(const std::string& args); + void stringToArgcArgv(const std::string& str, int* argc, char*** argv); +} #endif //STRINGTOARGCARGV_H