204 lines
5.8 KiB
C++
Executable File
204 lines
5.8 KiB
C++
Executable File
/*
|
|
* child.cpp
|
|
*
|
|
* Created on: Feb 11, 2019
|
|
* Author: hoo2
|
|
*/
|
|
|
|
#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
|
|
* @param cont
|
|
* @param delim
|
|
*/
|
|
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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ========== ArgList ==========
|
|
*/
|
|
ArgList::~ArgList() {
|
|
for (vtype p : args_) {
|
|
if (p != nullptr)
|
|
delete[] p;
|
|
}
|
|
}
|
|
ArgList& ArgList::push_back(const std::basic_string<type>& 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);
|
|
}
|
|
|
|
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};
|
|
|
|
for (auto& t: command) {
|
|
if (t == "" || t== " ") continue; // skip crap
|
|
|
|
if (t == "&&") logic = LogicOp::AND;
|
|
else if (t == "||") logic = LogicOp::OR;
|
|
// one pass redirection parsing
|
|
else if (!t.compare(0, 1, "<")) filenames[STDIN_] = t.substr(1);
|
|
else if (!t.compare(0, 1, ">")) filenames[STDOUT_] = t.substr(1);
|
|
else if (!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) {
|
|
filenames[STDIN_] = t; in = false;
|
|
}
|
|
else if (out) {
|
|
filenames[STDOUT_] = t; out = false;
|
|
}
|
|
else if (err) {
|
|
filenames[STDERR_] = t; err = false;
|
|
}
|
|
else
|
|
arguments.push_back(t);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
bool Child::execute() {
|
|
bool stop = {false};
|
|
|
|
redirect_std_if (filenames[STDIN_], STDIN_);
|
|
redirect_std_if (filenames[STDOUT_], STDOUT_);
|
|
redirect_std_if (filenames[STDERR_], STDERR_);
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
throw Error("Child::execute(): Can not create child process");
|
|
}
|
|
else if (pid == 0) { // child
|
|
|
|
execvp(arguments.front(), arguments.data());
|
|
// error if we got here
|
|
std::cout << "Can not invoke: " << arguments.front() << std::endl;
|
|
throw Error("Child::execute(): Can not run child process");
|
|
}
|
|
else { // parent
|
|
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<std::string> commands;
|
|
std::vector<std::string> 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<Child>{}); // make a new child for command
|
|
// check tokens inside command
|
|
for (auto& t: tokens) {
|
|
if (is_pipe(t)) {
|
|
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
seq_.back().emplace_back(command); // construct the child with tokens
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
Sequencer& Sequencer::execute() {
|
|
for (auto& batch : seq_)
|
|
for (auto& command: batch)
|
|
if (command.make_arguments().execute())
|
|
break;
|
|
seq_.clear();
|
|
return *this;
|
|
}
|
|
|
|
}
|