From 5d68248b18dff5e822771268cd204962bfaac042 Mon Sep 17 00:00:00 2001 From: Claudio Maggioni Date: Sat, 23 Apr 2016 13:03:53 +0200 Subject: [PATCH] Renamed some functions and variables in SIGINT handling. README updated. Removed "using" directives in the headers. Input, output and error streams are now fields of Shell. stringToArgcArgv has its own namespace. --- .gitignore | 2 +- README.md | 17 +- msh-console-library/CMakeLists.txt | 40 ++- msh-console-library/command.cpp | 42 ++- msh-console-library/command.h | 46 +++- msh-console-library/commandexecutor.cpp | 24 ++ msh-console-library/commandexecutor.h | 24 ++ msh-console-library/commands.cpp | 24 ++ msh-console-library/data.cpp | 24 ++ msh-console-library/datas.cpp | 28 +- msh-console-library/main.cpp | 75 ++++++ msh-console-library/shell.cpp | 202 +++++++++------ msh-console-library/shell.h | 77 ++++-- msh-console-library/stringtoargcargv.cpp | 311 ++++++++++++----------- msh-console-library/stringtoargcargv.h | 25 +- 15 files changed, 646 insertions(+), 315 deletions(-) create mode 100644 msh-console-library/main.cpp 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