From 8f36da884accbfb57255393002ae2eb0e120a824 Mon Sep 17 00:00:00 2001 From: Christos Houtouridis Date: Thu, 14 Feb 2019 10:18:52 +0200 Subject: [PATCH] Initial commit Interactive and batch mode bare implementation --- .gitignore | 4 + src/Snel.cpp | 32 ++++++++ src/sequencer.cpp | 197 ++++++++++++++++++++++++++++++++++++++++++++++ src/sequencer.h | 112 ++++++++++++++++++++++++++ 4 files changed, 345 insertions(+) create mode 100644 .gitignore create mode 100755 src/Snel.cpp create mode 100755 src/sequencer.cpp create mode 100755 src/sequencer.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5baa0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*Debug/* +*.cproject +*.project +*.settings/* diff --git a/src/Snel.cpp b/src/Snel.cpp new file mode 100755 index 0000000..2977a7c --- /dev/null +++ b/src/Snel.cpp @@ -0,0 +1,32 @@ +#include "sequencer.h" + + +int main(int argc, char* argv[]) try { + snel::Sequencer s{}; + std::string line; + + if (argc > 1) { // batch mode + std::ifstream file(argv[1]); + + while (std::getline (file, line, '\n')) { + s.parse(line).execute(); + } + } + else { // interactive mode + std::cout << "Snel. A quick and dirty shell implementation for auth.gr" << std::endl; + do { + std::cout << "Choutouridis_8997>"; + std::getline (std::cin, line, '\n'); + if (line == "quit") + break; + s.parse(line).execute(); + } while (1); + } + return 0; + +} +catch (std::exception& e) { + std::cerr << e.what() << '\n'; + exit(1); +} + diff --git a/src/sequencer.cpp b/src/sequencer.cpp new file mode 100755 index 0000000..92ee9e9 --- /dev/null +++ b/src/sequencer.cpp @@ -0,0 +1,197 @@ +/* + * child.cpp + * + * Created on: Feb 11, 2019 + * Author: hoo2 + */ + +#include "sequencer.h" + +namespace snel { + + + //! A type-safe memcpy + template + void memcpy (T* to, const T* from, size_t n) { + for (size_t i=0 ; i + void split(const std::basic_string& str, Container& cont, T delim) { + std::basic_stringstream ss(str); + std::basic_string token; + while (std::getline(ss, token, delim)) { + cont.push_back(token); + } + } + + /* + * ========== ArgList ========== + */ + ArgList::~ArgList() { + for (vtype p : args_) { + if (p != nullptr) + delete[] p; + } + } + ArgList& ArgList::push_back(const std::basic_string& item) { + vtype it = new type[item.length()+1]; + + // get data + memcpy (it, item.c_str(), item.length()); + it[item.length()] = 0; + + // update the argument vector + args_.back() = it; + args_.push_back(nullptr); + return *this; + } + + /* + * ========== Child ========== + */ + Child::~Child () { + for (int i=0 ; i<3 ; ++i) + restore_std_if(i); + } + + void Child::redirect_std_if(std::string fn, fd_t std_fd) { + if (fn != "") { + if ((files[std_fd] = openat(AT_FDCWD, fn.c_str(), O_RDWR | O_CREAT, 0640)) == -1) + throw Error ("Child: Can not open file"); + if ((sstd[std_fd] = dup (std_fd)) == -1) // save STDxxx + throw Error ("Child: Can not create file descriptor"); + if ((dup2(files[std_fd], std_fd)) == -1) // use input as STDIN_ + throw Error ("Child: Can not redirect file descriptor"); + } + } + void Child::restore_std_if(fd_t std_fd) { + if (sstd[std_fd] != -1) { + dup2(sstd[std_fd], std_fd); + close (sstd[std_fd]); + sstd[std_fd] = -1; + } + if (files[std_fd] != -1) { + close (files[std_fd]); + files[std_fd] = -1; + } + + } + Child& Child::make_arguments () { + bool in{false}, out{false}, err{false}; + + for (auto& t: command) { + if (t == "" || t== " ") + continue; + if (t == "&&") logic = LogicOp::AND; + else if (t == "||") logic = LogicOp::OR; + else if (t == "<") in = true; + else if (t == "1>" || t == ">") out = true; + else if (t == "2>") err = true; + else if (in) { + filenames[STDIN_] = t; in = false; + } + else if (out) { + filenames[STDOUT_] = t; out = false; + } + else if (err) { + filenames[STDERR_] = t; err = false; + } + else + arguments.push_back(t); + } + return *this; + } + + bool Child::execute() { + bool stop = {false}; + + redirect_std_if (filenames[STDIN_], STDIN_); + redirect_std_if (filenames[STDOUT_], STDOUT_); + redirect_std_if (filenames[STDERR_], STDERR_); + pid_t pid = fork(); + if (pid < 0) { + throw Error("Child::execute(): Can not create child process"); + } + else if (pid == 0) { // child + + execvp(arguments.front(), arguments.data()); + // error if we got here + std::cout << "Can not invoke: " << arguments.front() << std::endl; + throw Error("Child::execute(): Can not run child process"); + } + else { // parent + int exit_status; + waitpid(pid, &exit_status, 0); // wait child process to finish + restore_std_if (STDIN_); + restore_std_if (STDOUT_); + restore_std_if (STDERR_); + + switch (logic) { + case LogicOp::AND: + if (exit_status) stop = true; + break; + case LogicOp::OR: + if (!exit_status) stop = true; + break; + default: break; + } + } + + return stop; + } + + /* + * ======== Sequencer =========== + */ + Sequencer& Sequencer::parse (const std::string& input) { + std::vector commands; + std::vector tokens; + Child::command_t command; + + split (input, commands, ';'); // split all commands + for (auto& s : commands) { + command.clear(); //clear child tokens + tokens.clear(); // make a new token vector for command + split (s, tokens, ' '); + + seq_.emplace_back(std::vector{}); // make a new child for command + // check tokens inside command + for (auto& t: tokens) { + if (is_pipe(t)) { + + } + + command.push_back(t); // get current token + if (is_seperator(t)) { + // construct a child with what we have so far and clear command + seq_.back().emplace_back(command); + command.clear(); + } + } + seq_.back().emplace_back(command); // construct the child with tokens + } + + return *this; + } + + Sequencer& Sequencer::execute() { + for (auto& batch : seq_) + for (auto& command: batch) + if (command.make_arguments().execute()) + break; + seq_.clear(); + return *this; + } + +} diff --git a/src/sequencer.h b/src/sequencer.h new file mode 100755 index 0000000..a84f830 --- /dev/null +++ b/src/sequencer.h @@ -0,0 +1,112 @@ +/* + * child.h + * + * Created on: Feb 11, 2019 + * Author: hoo2 + */ + +#ifndef __sequencer_h__ +#define __sequencer_h__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +namespace snel { + + //! file descriptor type + using fd_t = int; + + constexpr fd_t STDIN_ = STDIN_FILENO; + constexpr fd_t STDOUT_ = STDOUT_FILENO; + constexpr fd_t STDERR_ = STDERR_FILENO; + + /*! + * + */ + struct ArgList { + using type = char; // Basic data type + using vtype = type*; // Vector type + using vtype_ptr = vtype*; // Pointer to vector type + + ~ArgList(); + ArgList() = default; + ArgList(const ArgList&) = default; + ArgList(ArgList&&) = default; + + ArgList& push_back(const std::basic_string& item); + vtype front () { return args_.front(); } + size_t size () { return args_.size(); } + + vtype_ptr data() noexcept { + return args_.data(); + } + vtype_ptr operator*() noexcept { + return data(); + } + private: + std::vector args_ {nullptr}; + }; + + + class Child { + public: + enum class LogicOp { NONE=0, OR, AND }; + using command_t = std::vector; + using Error = std::runtime_error; + + public: + ~Child (); + Child () noexcept = default; + Child (const command_t& c) : command{c} { } + Child (command_t&& c) : command{std::move(c)} { } + + Child& make_arguments (); + bool execute (); + private: + void redirect_std_if(std::string fn, fd_t std_fd); + void restore_std_if(fd_t std_fd); + private: + command_t command {}; + ArgList arguments {}; + + fd_t files[3] = {-1, -1, -1}; + fd_t sstd [3] = {-1, -1, -1}; + std::string filenames[3] = {"", "", ""}; + LogicOp logic {LogicOp::NONE}; + bool pipe {false}; + }; + + + class Sequencer { + public: + Sequencer& parse (const std::string& input); + Sequencer& execute (); + + private: + bool is_seperator (std::string& s) { + return (s == "&&" || s == "||") ? true : false; + } + bool is_pipe (std::string& s) { + return (s == "|") ? true : false; + } + + private: + std::vector < + std::vector + > seq_ {}; + }; + +} + +#endif /* __sequencer_h__ */