/* * child.cpp * * Created on: Feb 11, 2019 * Author: hoo2 */ #include "sequencer.h" namespace snel { //! A type-safe memcpy template void memcpy (T* to, const T* from, size_t n) { for (size_t i=0 ; i void split(const std::basic_string& str, Container& cont, T delim) { std::basic_stringstream ss(str); std::basic_string token; while (std::getline(ss, token, delim)) { cont.push_back(token); } } std::string filter (const std::string in) { std::string out; char prev {0}, cur, next {0}; for (auto i = in.begin(); i != in.end() ; ++i) { cur = *i; if (cur == '|') { if (prev != ' ') out += ' '; next = *++i; if (next == '|' || next == ' ') { out += cur; cur = next; // syntax ok } else { // syntax problem, insert a space out += cur; out += ' '; cur = next; } } prev = cur; out += cur; } return out; } /* * ========== ArgList ========== */ ArgList::~ArgList() { for (vtype p : args_) { if (p != nullptr) delete[] p; } } ArgList& ArgList::push_back(const std::string& item) { vtype it = new type[item.length()+1]; // get data memcpy (it, item.c_str(), item.length()); it[item.length()] = 0; // update the argument vector args_.back() = it; args_.push_back(nullptr); return *this; } /* * ========== Child ========== */ 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]); } 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"); } } 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; } } 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; } bool Child::execute(std::vector::iterator it, bool first) { bool stop = {false}; if (!first) --it; Child& previous = *it; // Check parent redirection 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 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 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 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_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; } } return stop; } /* * ======== Sequencer =========== */ Sequencer& Sequencer::parse (const std::string& input) { std::vector commands; std::vector tokens; 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, ' '); seq_.emplace_back(std::vector{}); // make a new child for command // check tokens inside command 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) { seq_.back().back().pipe().from = from; from = false; } if (is_pipe(t)) { seq_.back().back().pipe().to = true; from = true; } } } seq_.back().emplace_back(command); // construct the child with tokens if (from) seq_.back().back().pipe().from = from; } return *this; } Sequencer& Sequencer::execute() { for (auto& batch : seq_) { bool first; first = true; for (auto it = batch.begin() ; it != batch.end(); ++it) { Child& command = *it; if (command.make_arguments().execute(it, first)) break; first = false; } } seq_.clear(); return *this; } }