Some comments. minor changes and a Makefile
This commit is contained in:
parent
629f634f5d
commit
3c9e0a32fb
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,6 @@
|
|||||||
*Debug/*
|
*Debug/*
|
||||||
|
*bin/*
|
||||||
|
*doc/*
|
||||||
*.cproject
|
*.cproject
|
||||||
*.project
|
*.project
|
||||||
*.settings/*
|
*.settings/*
|
||||||
|
48
Makefile
Executable file
48
Makefile
Executable 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
|
||||||
|
|
||||||
|
|
35
src/Snel.cpp
35
src/Snel.cpp
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
135
src/sequencer.h
135
src/sequencer.h
@ -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_ {};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user