/*! * \file * Sequencer.h * \brief A basic sequence interpreter for snel * * Created on: Feb, 2019 * Author: Christos Choutouridis AEM: 8997 * email : cchoutou@ece.auth.gr */ #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; 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 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 { using type = typename std::string::value_type; // Basic data type (aka char) 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::string& item); //!< A vector::push_back wrapper vtype front () { return args_.front(); } //!< A vector::front() wrapper size_t size () { return args_.size(); } //!< A vector::size() wrapper //! return a pointer to underling data for `execvp()` vtype_ptr data() noexcept { return args_.data(); } //! same as `data()` vtype_ptr operator*() noexcept { return data(); } private: // data std::vector 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 { ~Pipe(); fd_t fd[2] {-1, -1}; bool from {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 { public: enum class LogicOp { NONE=0, OR, AND }; using command_t = std::vector; //!< A command type using Error = std::runtime_error; //!< An error type public: ~Child (); Child () = default; Child (const command_t& c) : command{c} { } Child (command_t&& c) : command{std::move(c)} { } Child& make_arguments (); //!< A per child parser before execution //! the actual execution routine bool execute(std::vector::iterator it, bool first); Pipe& pipe() { return pipe_; } //!< A pipe_ getter //! Private api //! @{ private: void redirect_std_if(std::string fn, fd_t std_fd); //!< tool to redirect std 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 { public: Sequencer& parse (const std::string& input); //!< Input line parser Sequencer& execute (); //!< Main sequencer executor //! private tools //! @{ private: //! separator trait predicate bool is_seperator (std::string& s) { return (s == "&&" || s == "||" || s == "|") ? true : false; } //! pipe trait predicate bool is_pipe (std::string& s) { return (s == "|") ? true : false; } //! @} private: //! The sequencer data representation std::vector < std::vector > seq_ {}; }; } #endif /* __sequencer_h__ */