|
- /*!
- * \file
- * Sequencer.cpp
- * \brief A basic sequence interpreter for snel
- *
- * 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) {
- for (size_t i=0 ; i<n ; ++i)
- *to++ = *from++;
- }
-
- /*!
- * Split a string to tokens and store them in a container, using a delimiter
- * character.
- * \note
- * Requires: container with push_back functionality
- * \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) {
- std::basic_stringstream<T> ss(str);
- std::basic_string<T> token;
- while (std::getline(ss, token, delim)) {
- cont.push_back(token);
- }
- }
-
- /*!
- * 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) {
- 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};
-
- // 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 += ' '; // prev| insert a space before
- next = *++i;
- if (next == '|' || next == ' ') {
- out += cur; cur = next; // syntax ok
- }
- else {
- out += cur; out += ' '; // |next, insert a space after
- cur = next;
- }
- }
- prev = cur;
- out += cur;
- }
- 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;
- }
-
- /*!
- * 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];
-
- memcpy (it, item.c_str(), item.length()); // get data and null terminate them
- it[item.length()] = 0;
-
- 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);
- }
-
- /*!
- * 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)
- 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");
- }
- }
- /*!
- * 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);
- close (sstd[std_fd]);
- sstd[std_fd] = -1;
- }
- if (files[std_fd] != -1) {
- close (files[std_fd]);
- files[std_fd] = -1;
- }
-
- }
-
- /*!
- * 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;
- bool CanRedirectOut = (!pipe_.to) ? true : false;
-
- for (auto& t: command) {
- if (t == "" || t== " " || t=="|") continue; // skip crap
-
- if (t == "&&") logic = LogicOp::AND;
- else if (t == "||") logic = LogicOp::OR;
- // one pass redirection parsing
- else if (CanRedirectIn && !t.compare(0, 1, "<"))
- filenames[STDIN_] = t.substr(1);
- else if (CanRedirectOut&& !t.compare(0, 1, ">"))
- filenames[STDOUT_] = t.substr(1);
- else if (CanRedirectOut&& !t.compare(0, 2, "1>"))
- filenames[STDOUT_] = t.substr(2);
- else if (!t.compare(0, 2, "2>"))
- filenames[STDERR_] = t.substr(2);
- // two pass redirection parsing (if redirection came in 2 arguments)
- else if (t == "<") in = true;
- else if (t == "1>" || t == ">") out = true;
- else if (t == "2>") err = true;
- else if (in) {
- if (CanRedirectIn)
- filenames[STDIN_] = t;
- in = false;
- }
- else if (out) {
- if (CanRedirectOut)
- filenames[STDOUT_] = t;
- out = false;
- }
- else if (err) {
- filenames[STDERR_] = t; err = false;
- }
- else
- arguments.push_back(t);
- }
- return *this;
- }
-
- /*!
- * \brief Execute the child in separate process.
- *
- * This is the hart of the snel. We use <tt>fork() - execvp()</tt> 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:
- * \code ls && cat tralala || uname -a \endcode
- * is a chain with three childs (ls, cat, uname). \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};
- Child& previous = (!first) ? *--it : *it; // get previous child safely
-
- // Check redirection requirements
- redirect_std_if (filenames[STDIN_], STDIN_);
- redirect_std_if (filenames[STDOUT_], STDOUT_);
- redirect_std_if (filenames[STDERR_], STDERR_);
-
- // Check parent pipe control
- if (pipe_.to) {
- if((::pipe (pipe_.fd)) == -1)
- throw Error("Child: Can not create pipe");
- }
- pid_t pid = fork();
- if (pid < 0) {
- throw Error("Child: Can not create child process");
- }
- 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 (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) { // 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]); // 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_); // restore all redirections
- restore_std_if (STDOUT_);
- restore_std_if (STDERR_);
-
- // 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> chains; // chains are vector of commands
- std::vector<std::string> tokens; // token is any ' ' separated string
- Child::command_t command;
-
- 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 the chain
- bool from = false;
- for (auto& t: tokens) {
- 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; // mark as transmitting child
- from = true; // mark to inform the next child as receiving
- }
- }
- }
- seq_.back().emplace_back(command); // construct the child with tokens
- if (from)
- seq_.back().back().pipe().from = from;
- }
- 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() {
- bool first;
- for (auto& chain : seq_) { // loop in all of the command chains
- first = true;
- 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 the chain if child's logic operator says so.
- first = false;
- }
- }
- seq_.clear(); // clear the sequencer's data for the next line (call all the destructors)
- return *this;
- }
-
- }
|