Some comments. minor changes and a Makefile

This commit is contained in:
Christos Houtouridis 2019-02-15 16:38:42 +02:00
parent 629f634f5d
commit 3c9e0a32fb
6 changed files with 2816 additions and 108 deletions

2
.gitignore vendored
View File

@ -1,4 +1,6 @@
*Debug/* *Debug/*
*bin/*
*doc/*
*.cproject *.cproject
*.project *.project
*.settings/* *.settings/*

2494
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

48
Makefile Executable file
View File

@ -0,0 +1,48 @@
#
# Snel makefile
#
# Author: Christos Choutouridis AEM:8997
# email: cchoutou@ece.auth.gr
#
CXX := -c++
CXXFLAGS := -std=c++14 -Wall -Wextra -Werror
LDFLAGS := -lstdc++
BUILD := ./bin
OBJ_DIR := $(BUILD)/obj
APP_DIR := $(BUILD)
TARGET := snel
SRC := $(wildcard src/*.cpp)
OBJECTS := $(SRC:%.cpp=$(OBJ_DIR)/%.o)
$(OBJ_DIR)/%.o: %.cpp
@mkdir -p $(@D)
$(CXX) $(CXXFLAGS) -o $@ -c $<
$(APP_DIR)/$(TARGET): $(OBJECTS)
@mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(APP_DIR)/$(TARGET) $(OBJECTS)
# === Rules ===
snel: build $(APP_DIR)/$(TARGET)
build:
@mkdir -p $(APP_DIR)
@mkdir -p $(OBJ_DIR)
debug: CXXFLAGS += -DDEBUG -g3
debug: snel
release: CXXFLAGS += -O2
release: snel
all: release
clean:
-@rm -rvf $(OBJ_DIR)/*
-@rm -rvf $(APP_DIR)/*
.PHONY: snel build debug release all clean

View File

@ -1,32 +1,45 @@
/*!
* \file
* Snel.cpp
* \brief A shell implementation for A.U.TH (Operating systems Lab)
*
* Created on: Feb, 2019
* Author: Christos Choutouridis AEM: 8997
* email : cchoutou@ece.auth.gr
*/
#include "sequencer.h" #include "sequencer.h"
int main(int argc, char* argv[]) try { int main(int argc, char* argv[]) try {
snel::Sequencer s{}; snel::Sequencer seq{};
std::string line; std::string line, filtered;
if (argc > 1) { // batch mode if (argc > 1) { // batch mode
std::ifstream file(argv[1]); std::ifstream file(argv[1]);
while (std::getline (file, line, '\n')) { while (std::getline (file, line, '\n')) {
s.parse(snel::filter(line)).execute(); //^ We use \n as delimiter. No further parsing here
filtered = snel::filter(line); // simple filtering
if (filtered == "quit")
break;
seq.parse(filtered).execute();
} }
} }
else { // interactive mode else { // interactive mode
std::cout << "Snel. A quick and dirty shell implementation for auth.gr" << std::endl; std::cout << "Snel. A shell implementation for A.U.TH (OS Lab)." << std::endl;
do { do {
std::cout << "Choutouridis_8997>"; std::cout << "Choutouridis_8997>";
std::getline (std::cin, line, '\n'); std::getline (std::cin, line, '\n'); //< We use \n as delimiter. No parsing here
if (line == "quit") filtered = snel::filter(line); // simple filtering
if (filtered == "quit")
break; break;
s.parse(snel::filter(line)).execute(); seq.parse(filtered).execute();
} while (1); } while (1);
} }
return 0; return 0;
} } catch (std::exception& e) {
catch (std::exception& e) { // we probably pollute the user's screen. Comment `cerr << ...` if you don't like it.
std::cerr << e.what() << '\n'; // std::cerr << e.what() << '\n';
exit(1); exit(1);
} }

View File

@ -1,15 +1,16 @@
/* /*!
* child.cpp * \file
* Sequencer.cpp
* \brief A basic sequence interpreter for snel
* *
* Created on: Feb 11, 2019 * Created on: Feb, 2019
* Author: hoo2 * Author: Christos Choutouridis AEM: 8997
* email : cchoutou@ece.auth.gr
*/ */
#include "sequencer.h" #include "sequencer.h"
namespace snel { namespace snel {
//! A type-safe memcpy //! A type-safe memcpy
template <typename T> template <typename T>
void memcpy (T* to, const T* from, size_t n) { void memcpy (T* to, const T* from, size_t n) {
@ -22,9 +23,9 @@ namespace snel {
* character. * character.
* \note * \note
* Requires: container with push_back functionality * Requires: container with push_back functionality
* @param str * \param str The input string to split
* @param cont * \param cont The container to push the tokens (MUST have .push_back(T) member)
* @param delim * \param delim The delimiter of type \p T to use
*/ */
template <typename T, typename Container> template <typename T, typename Container>
void split(const std::basic_string<T>& str, Container& cont, T delim) { void split(const std::basic_string<T>& str, Container& cont, T delim) {
@ -35,21 +36,38 @@ namespace snel {
} }
} }
/*!
* A very very simple filtering for leading ' ', comments and adjustments for
* the '|' character.
* \note
* The current snel implementation requires '|' to separated
* from left and right from the rest of the text, so we adjust the input to fit
* this expectation.
* \param in Input string
* \return Output string
*/
std::string filter (const std::string in) { std::string filter (const std::string in) {
std::string out; auto first = in.find_first_not_of(' ');
std::string nonws = (first != std::string::npos) ?
in.substr(first) : "";
std::string out{};
char prev {0}, cur, next {0}; char prev {0}, cur, next {0};
for (auto i = in.begin(); i != in.end() ; ++i) { // return empty if the first non-whitespace character is '#'
if (nonws[0] == '#')
return out;
for (auto i = nonws.begin(); i != nonws.end() ; ++i) {
cur = *i; cur = *i;
if (cur == '|') { if (cur == '|') {
if (prev != ' ') out += ' '; if (prev != ' ') out += ' '; // prev| insert a space before
next = *++i; next = *++i;
if (next == '|' || next == ' ') { if (next == '|' || next == ' ') {
out += cur; cur = next; // syntax ok out += cur; cur = next; // syntax ok
} }
else { else {
// syntax problem, insert a space out += cur; out += ' '; // |next, insert a space after
out += cur; out += ' '; cur = next; cur = next;
} }
} }
prev = cur; prev = cur;
@ -57,39 +75,64 @@ namespace snel {
} }
return out; return out;
} }
/* /*
* ========== ArgList ========== * ========== ArgList ==========
*/ */
/*!
* destructor to free up all the allocations
* \note
* Using RAII for ArgList we ensure "no memory leak"
*/
ArgList::~ArgList() { ArgList::~ArgList() {
for (vtype p : args_) { for (vtype p : args_)
if (p != nullptr) if (p != nullptr) delete[] p;
delete[] p;
}
} }
/*!
* A push_back wrapper to our underling vector. This ensures NULL terminating
* argument list for exec() sys-call.
* \param item The argument to push
* \return Reference to ArgList for chaining
*/
ArgList& ArgList::push_back(const std::string& item) { ArgList& ArgList::push_back(const std::string& item) {
vtype it = new type[item.length()+1]; vtype it = new type[item.length()+1];
// get data memcpy (it, item.c_str(), item.length()); // get data and null terminate them
memcpy (it, item.c_str(), item.length());
it[item.length()] = 0; it[item.length()] = 0;
// update the argument vector args_.back() = it; // update the argument vector
args_.back() = it;
args_.push_back(nullptr); args_.push_back(nullptr);
return *this; return *this;
} }
/*
* ========== Pipe ===========
*/
//! Destructor to free up all the resources
Pipe::~Pipe() {
// close any possibly leaked pipes
if (fd[0] != -1) close(fd[0]);
if (fd[1] != -1) close(fd[1]);
}
/* /*
* ========== Child ========== * ========== Child ==========
*/ */
//! Destructor to free up all the resources
Child::~Child () { Child::~Child () {
for (int i=0 ; i<3 ; ++i) for (int i=0 ; i<3 ; ++i)
restore_std_if(i); restore_std_if(i);
// close any leaked pipes
// if (pipe_.fd[0] != -1) close(pipe_.fd[0]);
// if (pipe_.fd[1] != -1) close(pipe_.fd[1]);
} }
/*!
* Redirect a \p std_fd file descriptor to a file
* @param fn The file name to used as std
* @param std_fd The file descriptor to redirect
*/
void Child::redirect_std_if(std::string fn, fd_t std_fd) { void Child::redirect_std_if(std::string fn, fd_t std_fd) {
if (fn != "") { if (fn != "") {
if ((files[std_fd] = openat(AT_FDCWD, fn.c_str(), O_RDWR | O_CREAT, 0640)) == -1) if ((files[std_fd] = openat(AT_FDCWD, fn.c_str(), O_RDWR | O_CREAT, 0640)) == -1)
@ -100,6 +143,10 @@ namespace snel {
throw Error ("Child: Can not redirect file descriptor"); throw Error ("Child: Can not redirect file descriptor");
} }
} }
/*!
* Restore and redirected std file descriptor to its original location
* \param std_fd The file descriptor to restore
*/
void Child::restore_std_if(fd_t std_fd) { void Child::restore_std_if(fd_t std_fd) {
if (sstd[std_fd] != -1) { if (sstd[std_fd] != -1) {
dup2(sstd[std_fd], std_fd); dup2(sstd[std_fd], std_fd);
@ -112,6 +159,11 @@ namespace snel {
} }
} }
/*!
* Parse the command of the current child to produce the actual arguments
* \return Reference to Child for chaining
*/
Child& Child::make_arguments () { Child& Child::make_arguments () {
bool in{false}, out{false}, err{false}; bool in{false}, out{false}, err{false};
bool CanRedirectIn = (!pipe_.from) ? true : false; bool CanRedirectIn = (!pipe_.from) ? true : false;
@ -154,12 +206,27 @@ namespace snel {
return *this; return *this;
} }
/*!
* \brief Execute the child in separate process.
*
* This is the hart of the snel. We use fork() - execvp() pair to execute
* each child. In case of std redirection, the fd/dup2 handling is made in the
* parent. In case of piping. The fd handling is made in parent and the
* dup2() calls in the child process.
*
* A child is a member of a vector containing the command chain.
* For ex: `ls && cat tralala || uname -a` is a chain with three
* childs (ls, cat, uname). See also \see Sequencer
*
* \param it Iterator to the command chain execution
* \param first flag to indicate the first command in the chain
* \return True if the chain can stop.
*/
bool Child::execute(std::vector<Child>::iterator it, bool first) { bool Child::execute(std::vector<Child>::iterator it, bool first) {
bool stop = {false}; bool stop {false};
if (!first) --it; Child& previous = (!first) ? *--it : *it; // get previous child safely
Child& previous = *it;
// Check parent redirection // Check redirection requirements
redirect_std_if (filenames[STDIN_], STDIN_); redirect_std_if (filenames[STDIN_], STDIN_);
redirect_std_if (filenames[STDOUT_], STDOUT_); redirect_std_if (filenames[STDOUT_], STDOUT_);
redirect_std_if (filenames[STDERR_], STDERR_); redirect_std_if (filenames[STDERR_], STDERR_);
@ -173,64 +240,74 @@ namespace snel {
if (pid < 0) { if (pid < 0) {
throw Error("Child: Can not create child process"); throw Error("Child: Can not create child process");
} }
else if (pid == 0) { // child else if (pid == 0) {
// =========== child ================
// Some extra pipe checking while we still have our data // Some extra pipe checking while we still have our data
if (pipe_.to) { // transmitting child if (pipe_.to) { // transmitting child
close(pipe_.fd[0]); // close the read end close(pipe_.fd[0]); // close the read end (of the child copy)
if ((dup2(pipe_.fd[1], STDOUT_)) == -1) // redirect output if ((dup2(pipe_.fd[1], STDOUT_)) == -1) // redirect output
throw Error("Child pipe[to]: Can not redirect through pipe"); throw Error("Child pipe[to]: Can not redirect through pipe");
} }
else if (!first && pipe_.from) { // receiveing child else if (!first && pipe_.from) { // receiving child
close (previous.pipe().fd[1]); // close the write end close (previous.pipe().fd[1]); // close the write end (of the child copy)
if ((dup2(previous.pipe().fd[0], STDIN_)) == -1) // redirect input if ((dup2(previous.pipe().fd[0], STDIN_)) == -1) // redirect input
throw Error("Child pipe[from]: Can not redirect through pipe"); throw Error("Child pipe[from]: Can not redirect through pipe");
} }
execvp(arguments.front(), arguments.data()); execvp(arguments.front(), arguments.data());
// error if we got here // error if we got here
std::cout << "Can not invoke: " << arguments.front() << std::endl; std::cout << "Can not invoke: " << arguments.front() << std::endl;
throw Error("Child: Can not run child process"); throw Error("Child: Can not run child process");
} }
else { // parent else {
if (pipe_.to) close (pipe_.fd[1]); // =========== parent ================
if (pipe_.to) close (pipe_.fd[1]); // Pipe fd control
else if (!first && pipe_.from) else if (!first && pipe_.from)
close (previous.pipe().fd[0]); close (previous.pipe().fd[0]);
int exit_status; int exit_status;
waitpid(pid, &exit_status, 0); // wait child process to finish waitpid(pid, &exit_status, 0); // wait child process to finish
restore_std_if (STDIN_); restore_std_if (STDIN_); // restore all redirections
restore_std_if (STDOUT_); restore_std_if (STDOUT_);
restore_std_if (STDERR_); restore_std_if (STDERR_);
switch (logic) { // Check if we don't have to execute the rest of the chain
case LogicOp::AND: if ( exit_status && logic == LogicOp::AND) stop = true;
if (exit_status) stop = true; if (!exit_status && logic == LogicOp::OR) stop = true;
break;
case LogicOp::OR:
if (!exit_status) stop = true;
break;
default: break;
}
} }
return stop; return stop;
} }
/* /*
* ======== Sequencer =========== * ======== Sequencer ===========
*/ */
/*!
* First parsing of each line. We split input in ';' to create
* command chains and split each chain further to create child tokens.
* These token will be processed further by the child's `make_arguments()`
*
* \param input The input string to parse
* \return Reference to Sequencer for chaining
*/
Sequencer& Sequencer::parse (const std::string& input) { Sequencer& Sequencer::parse (const std::string& input) {
std::vector<std::string> commands; std::vector<std::string> chains; // chains are vector of commands
std::vector<std::string> tokens; std::vector<std::string> tokens; // token is any ' ' separated string
Child::command_t command; Child::command_t command;
split (input, commands, ';'); // split all commands split (input, chains, ';'); // split input to command chains using ';'
for (auto& s : commands) { for (auto& chain : chains) { // for each chain
command.clear(); //clear child tokens command.clear(); // clear child tokens
tokens.clear(); // make a new token vector for command tokens.clear(); // make a new token vector for command
split (s, tokens, ' '); split (chain, tokens, ' '); // split chain in to tokens using ' '
seq_.emplace_back(std::vector<Child>{}); // make a new child for command seq_.emplace_back(std::vector<Child>{}); // make a new child for command
// check tokens inside command // check tokens inside the chain
bool from = false; bool from = false;
for (auto& t: tokens) { for (auto& t: tokens) {
command.push_back(t); // get current token command.push_back(t); // get current token
@ -239,13 +316,14 @@ namespace snel {
seq_.back().emplace_back(command); seq_.back().emplace_back(command);
command.clear(); command.clear();
if (from) { if (from) {
// set from flag to link with the previous child
seq_.back().back().pipe().from = from; seq_.back().back().pipe().from = from;
from = false; from = false;
} }
if (is_pipe(t)) { if (is_pipe(t)) {
seq_.back().back().pipe().to = true; seq_.back().back().pipe().to = true; // mark as transmitting child
from = true; from = true; // mark to inform the next child as receiving
} }
} }
} }
@ -256,18 +334,24 @@ namespace snel {
return *this; return *this;
} }
/*!
* The main sequencer execution. We recurse for each command chain
* and each child in the chain and we parse-execute the child
* \return Reference to Sequence for chaining
*/
Sequencer& Sequencer::execute() { Sequencer& Sequencer::execute() {
for (auto& batch : seq_) {
bool first; bool first;
for (auto& chain : seq_) { // loop in all of the command chains
first = true; first = true;
for (auto it = batch.begin() ; it != batch.end(); ++it) { for (auto it = chain.begin() ; it != chain.end(); ++it) {
// loop all of the commands in the chain
Child& command = *it; Child& command = *it;
if (command.make_arguments().execute(it, first)) if (command.make_arguments().execute(it, first))
break; break; // break the chain if child's logic operator says so.
first = false; first = false;
} }
} }
seq_.clear(); seq_.clear(); // clear the sequencer's data for the next line (call all the destructors)
return *this; return *this;
} }

View File

@ -1,10 +1,12 @@
/* /*!
* child.h * \file
* Sequencer.h
* \brief A basic sequence interpreter for snel
* *
* Created on: Feb 11, 2019 * Created on: Feb, 2019
* Author: hoo2 * Author: Christos Choutouridis AEM: 8997
* email : cchoutou@ece.auth.gr
*/ */
#ifndef __sequencer_h__ #ifndef __sequencer_h__
#define __sequencer_h__ #define __sequencer_h__
@ -34,10 +36,23 @@ namespace snel {
std::string filter (const std::string in); std::string filter (const std::string in);
/*! /*!
* A vector based wrapper above execvp()'s `char* argv[]` interface.
* We use a vector for convenience and resizing capabilities. We can then
* use a pointer to underling data to pass to execvp()
* std::vector elements guaranteed to be contiguous. To quote the standard[1]
* \code
* The elements of a vector are stored contiguously, meaning that if v is
* a vector<T, Allocator> where T is some type other than bool, then it obeys
* the identity &v[n] == &v[0] + n for all 0 <= n < v.size().
* \endcode
* *
* See also cppreference[2].
*
* [1]: www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf
* [2]: https://en.cppreference.com/w/cpp/container/vector
*/ */
struct ArgList { struct ArgList {
using type = typename std::string::value_type; // Basic data type using type = typename std::string::value_type; // Basic data type (aka char)
using vtype = type*; // Vector type using vtype = type*; // Vector type
using vtype_ptr = vtype*; // Pointer to vector type using vtype_ptr = vtype*; // Pointer to vector type
@ -46,32 +61,48 @@ namespace snel {
ArgList(const ArgList&) = default; ArgList(const ArgList&) = default;
ArgList(ArgList&&) = default; ArgList(ArgList&&) = default;
ArgList& push_back(const std::string& item); ArgList& push_back(const std::string& item); //!< A vector::push_back wrapper
vtype front () { return args_.front(); } vtype front () { return args_.front(); } //!< A vector::front() wrapper
size_t size () { return args_.size(); } size_t size () { return args_.size(); } //!< A vector::size() wrapper
vtype_ptr data() noexcept { //! return a pointer to underling data for `execvp()`
return args_.data(); vtype_ptr data() noexcept { return args_.data(); }
} //! same as `data()`
vtype_ptr operator*() noexcept { vtype_ptr operator*() noexcept { return data(); }
return data();
}
private: private:
// data
std::vector<vtype> args_ {nullptr}; std::vector<vtype> args_ {nullptr};
}; };
/*!
* A pipe file descriptor container for each child.
* \note
* We store the pipe in the first child and use the flags `from`
* and `to` to inform child's executer for where can find the data.
*/
struct Pipe { struct Pipe {
~Pipe();
fd_t fd[2] {-1, -1}; fd_t fd[2] {-1, -1};
bool from {false}; bool from {false};
bool to {false}; bool to {false};
}; };
/*!
* An object to represent each process
*
* We create Child objects from command_t passed from Sequence::parse() and we
* keep in each child informations about the file descriptors that may the process
* will use, pipes that may need and logic flags(&&, ||) the command may have to control
* the execution flow.
*/
class Child { class Child {
public: public:
enum class LogicOp { NONE=0, OR, AND }; enum class LogicOp {
using command_t = std::vector<std::string>; NONE=0, OR, AND
using Error = std::runtime_error; };
using command_t = std::vector<std::string>; //!< A command type
using Error = std::runtime_error; //!< An error type
public: public:
~Child (); ~Child ();
@ -79,37 +110,73 @@ namespace snel {
Child (const command_t& c) : command{c} { } Child (const command_t& c) : command{c} { }
Child (command_t&& c) : command{std::move(c)} { } Child (command_t&& c) : command{std::move(c)} { }
Child& make_arguments (); Child& make_arguments (); //!< A per child parser before execution
//! the actual execution routine
bool execute(std::vector<Child>::iterator it, bool first); bool execute(std::vector<Child>::iterator it, bool first);
Pipe& pipe() { return pipe_; } Pipe& pipe() { return pipe_; } //!< A pipe_ getter
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}; //! Private api
fd_t sstd [3] = {-1, -1, -1}; //! @{
std::string filenames[3] = {"", "", ""}; private:
LogicOp logic {LogicOp::NONE}; void redirect_std_if(std::string fn, fd_t std_fd); //!< tool to redirect std
Pipe pipe_; void restore_std_if(fd_t std_fd); //!< tool to restor std redirection
//! @}
//! Data members
//! \note
//! `arguments`, `files`, `sstd` and `pipe_` have linked resources so destructor(s) must be called.
//! We use RAII by having all children to static allocated vectors. '}' will do the trick even
//! if we throw ;)
//! @{
private:
command_t command {}; //!< Each command is a vector of string as passed on from Sequence::parse()
ArgList arguments {}; //!< The after parsing, actual null terminated arguments to pass to execvp()
fd_t files[3] = {-1, -1, -1}; //!< file descriptors for the user requested redirections
fd_t sstd [3] = {-1, -1, -1}; //!< file descriptors to store std fd's before redirection
std::string filenames[3] = {"", "", ""}; //!< filenames of the user requested redirections
LogicOp logic {LogicOp::NONE}; //!< Logic flags to store && and || command separators
Pipe pipe_; //!< Pipe object for each child
//! @}
}; };
/*!
* The main object to represent and handle a command flow.
*
* We store commands as vector of vectors of commands. The inner vector
* represent a command chain. For example the line:
* \code ls | more && uname; cat lala \endcode
* is an input with 2 command chains and result to a:
* \code
* vector<
* vector<(ls |), (more &&), (uname)>,
* vector<(cat)>
* >
* \endcode
* By storing them in different vector we make the execution model simpler.
* Each chain can stopped and the sequencer will continue with the next chain etc...
* So in this example if `more` returns false(child::execute() returned true),
* then `uname` will not parsed and executed and the flow will continue to `cat`
*/
class Sequencer { class Sequencer {
public: public:
Sequencer& parse (const std::string& input); Sequencer& parse (const std::string& input); //!< Input line parser
Sequencer& execute (); Sequencer& execute (); //!< Main sequencer executor
//! private tools
//! @{
private: private:
//! separator trait predicate
bool is_seperator (std::string& s) { bool is_seperator (std::string& s) {
return (s == "&&" || s == "||" || s == "|") ? true : false; return (s == "&&" || s == "||" || s == "|") ? true : false;
} }
//! pipe trait predicate
bool is_pipe (std::string& s) { bool is_pipe (std::string& s) {
return (s == "|") ? true : false; return (s == "|") ? true : false;
} }
//! @}
private: private:
//! The sequencer data representation
std::vector < std::vector <
std::vector <Child> std::vector <Child>
> seq_ {}; > seq_ {};