|
|
@@ -1,15 +1,16 @@ |
|
|
|
/* |
|
|
|
* child.cpp |
|
|
|
/*! |
|
|
|
* \file |
|
|
|
* Sequencer.cpp |
|
|
|
* \brief A basic sequence interpreter for snel |
|
|
|
* |
|
|
|
* Created on: Feb 11, 2019 |
|
|
|
* Author: hoo2 |
|
|
|
* Created on: Feb, 2019 |
|
|
|
* Author: Christos Choutouridis AEM: 8997 |
|
|
|
* email : cchoutou@ece.auth.gr |
|
|
|
*/ |
|
|
|
|
|
|
|
#include "sequencer.h" |
|
|
|
|
|
|
|
namespace snel { |
|
|
|
|
|
|
|
|
|
|
|
//! A type-safe memcpy |
|
|
|
template <typename T> |
|
|
|
void memcpy (T* to, const T* from, size_t n) { |
|
|
@@ -22,9 +23,9 @@ namespace snel { |
|
|
|
* character. |
|
|
|
* \note |
|
|
|
* Requires: container with push_back functionality |
|
|
|
* @param str |
|
|
|
* @param cont |
|
|
|
* @param delim |
|
|
|
* \param str The input string to split |
|
|
|
* \param cont The container to push the tokens (MUST have .push_back(T) member) |
|
|
|
* \param delim The delimiter of type \p T to use |
|
|
|
*/ |
|
|
|
template <typename T, typename Container> |
|
|
|
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 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}; |
|
|
|
|
|
|
|
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; |
|
|
|
if (cur == '|') { |
|
|
|
if (prev != ' ') out += ' '; |
|
|
|
if (prev != ' ') out += ' '; // prev| insert a space before |
|
|
|
next = *++i; |
|
|
|
if (next == '|' || next == ' ') { |
|
|
|
out += cur; cur = next; // syntax ok |
|
|
|
out += cur; cur = next; // syntax ok |
|
|
|
} |
|
|
|
else { |
|
|
|
// syntax problem, insert a space |
|
|
|
out += cur; out += ' '; cur = next; |
|
|
|
out += cur; out += ' '; // |next, insert a space after |
|
|
|
cur = next; |
|
|
|
} |
|
|
|
} |
|
|
|
prev = cur; |
|
|
@@ -57,39 +75,64 @@ namespace snel { |
|
|
|
} |
|
|
|
return out; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
* ========== ArgList ========== |
|
|
|
*/ |
|
|
|
/*! |
|
|
|
* destructor to free up all the allocations |
|
|
|
* \note |
|
|
|
* Using RAII for ArgList we ensure "no memory leak" |
|
|
|
*/ |
|
|
|
ArgList::~ArgList() { |
|
|
|
for (vtype p : args_) { |
|
|
|
if (p != nullptr) |
|
|
|
delete[] p; |
|
|
|
} |
|
|
|
for (vtype p : args_) |
|
|
|
if (p != nullptr) 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) { |
|
|
|
vtype it = new type[item.length()+1]; |
|
|
|
|
|
|
|
// get data |
|
|
|
memcpy (it, item.c_str(), item.length()); |
|
|
|
memcpy (it, item.c_str(), item.length()); // get data and null terminate them |
|
|
|
it[item.length()] = 0; |
|
|
|
|
|
|
|
// update the argument vector |
|
|
|
args_.back() = it; |
|
|
|
args_.back() = it; // update the argument vector |
|
|
|
args_.push_back(nullptr); |
|
|
|
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 ========== |
|
|
|
*/ |
|
|
|
|
|
|
|
//! Destructor to free up all the resources |
|
|
|
Child::~Child () { |
|
|
|
for (int i=0 ; i<3 ; ++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) { |
|
|
|
if (fn != "") { |
|
|
|
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"); |
|
|
|
} |
|
|
|
} |
|
|
|
/*! |
|
|
|
* 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) { |
|
|
|
if (sstd[std_fd] != -1) { |
|
|
|
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 () { |
|
|
|
bool in{false}, out{false}, err{false}; |
|
|
|
bool CanRedirectIn = (!pipe_.from) ? true : false; |
|
|
@@ -154,12 +206,27 @@ namespace snel { |
|
|
|
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 stop = {false}; |
|
|
|
if (!first) --it; |
|
|
|
Child& previous = *it; |
|
|
|
bool stop {false}; |
|
|
|
Child& previous = (!first) ? *--it : *it; // get previous child safely |
|
|
|
|
|
|
|
// Check parent redirection |
|
|
|
// Check redirection requirements |
|
|
|
redirect_std_if (filenames[STDIN_], STDIN_); |
|
|
|
redirect_std_if (filenames[STDOUT_], STDOUT_); |
|
|
|
redirect_std_if (filenames[STDERR_], STDERR_); |
|
|
@@ -173,79 +240,90 @@ namespace snel { |
|
|
|
if (pid < 0) { |
|
|
|
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 |
|
|
|
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 |
|
|
|
throw Error("Child pipe[to]: Can not redirect through pipe"); |
|
|
|
} |
|
|
|
else if (!first && pipe_.from) { // receiveing child |
|
|
|
close (previous.pipe().fd[1]); // close the write end |
|
|
|
else if (!first && pipe_.from) { // receiving child |
|
|
|
close (previous.pipe().fd[1]); // close the write end (of the child copy) |
|
|
|
if ((dup2(previous.pipe().fd[0], STDIN_)) == -1) // redirect input |
|
|
|
throw Error("Child pipe[from]: Can not redirect through pipe"); |
|
|
|
} |
|
|
|
|
|
|
|
execvp(arguments.front(), arguments.data()); |
|
|
|
// error if we got here |
|
|
|
std::cout << "Can not invoke: " << arguments.front() << std::endl; |
|
|
|
throw Error("Child: Can not run child process"); |
|
|
|
} |
|
|
|
else { // parent |
|
|
|
if (pipe_.to) close (pipe_.fd[1]); |
|
|
|
else { |
|
|
|
// =========== parent ================ |
|
|
|
if (pipe_.to) close (pipe_.fd[1]); // Pipe fd control |
|
|
|
else if (!first && pipe_.from) |
|
|
|
close (previous.pipe().fd[0]); |
|
|
|
|
|
|
|
int exit_status; |
|
|
|
waitpid(pid, &exit_status, 0); // wait child process to finish |
|
|
|
restore_std_if (STDIN_); |
|
|
|
waitpid(pid, &exit_status, 0); // wait child process to finish |
|
|
|
restore_std_if (STDIN_); // restore all redirections |
|
|
|
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; |
|
|
|
} |
|
|
|
// Check if we don't have to execute the rest of the chain |
|
|
|
if ( exit_status && logic == LogicOp::AND) stop = true; |
|
|
|
if (!exit_status && logic == LogicOp::OR) stop = true; |
|
|
|
} |
|
|
|
|
|
|
|
return stop; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
* ======== 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) { |
|
|
|
std::vector<std::string> commands; |
|
|
|
std::vector<std::string> tokens; |
|
|
|
std::vector<std::string> chains; // chains are vector of commands |
|
|
|
std::vector<std::string> tokens; // token is any ' ' separated string |
|
|
|
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, ' '); |
|
|
|
split (input, chains, ';'); // split input to command chains using ';' |
|
|
|
for (auto& chain : chains) { // for each chain |
|
|
|
command.clear(); // clear child tokens |
|
|
|
tokens.clear(); // make a new token vector for command |
|
|
|
split (chain, tokens, ' '); // split chain in to tokens using ' ' |
|
|
|
|
|
|
|
seq_.emplace_back(std::vector<Child>{}); // make a new child for command |
|
|
|
// check tokens inside command |
|
|
|
// check tokens inside the chain |
|
|
|
bool from = false; |
|
|
|
for (auto& t: tokens) { |
|
|
|
command.push_back(t); // get current token |
|
|
|
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(); |
|
|
|
if (from) { |
|
|
|
// set from flag to link with the previous child |
|
|
|
seq_.back().back().pipe().from = from; |
|
|
|
from = false; |
|
|
|
} |
|
|
|
|
|
|
|
if (is_pipe(t)) { |
|
|
|
seq_.back().back().pipe().to = true; |
|
|
|
from = true; |
|
|
|
seq_.back().back().pipe().to = true; // mark as transmitting child |
|
|
|
from = true; // mark to inform the next child as receiving |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@@ -256,18 +334,24 @@ namespace snel { |
|
|
|
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() { |
|
|
|
for (auto& batch : seq_) { |
|
|
|
bool first; |
|
|
|
bool first; |
|
|
|
for (auto& chain : seq_) { // loop in all of the command chains |
|
|
|
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; |
|
|
|
if (command.make_arguments().execute(it, first)) |
|
|
|
break; |
|
|
|
break; // break the chain if child's logic operator says so. |
|
|
|
first = false; |
|
|
|
} |
|
|
|
} |
|
|
|
seq_.clear(); |
|
|
|
seq_.clear(); // clear the sequencer's data for the next line (call all the destructors) |
|
|
|
return *this; |
|
|
|
} |
|
|
|
|
|
|
|