Initial commit
Interactive and batch mode bare implementation
This commit is contained in:
commit
8f36da884a
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*Debug/*
|
||||
*.cproject
|
||||
*.project
|
||||
*.settings/*
|
32
src/Snel.cpp
Executable file
32
src/Snel.cpp
Executable file
@ -0,0 +1,32 @@
|
||||
#include "sequencer.h"
|
||||
|
||||
|
||||
int main(int argc, char* argv[]) try {
|
||||
snel::Sequencer s{};
|
||||
std::string line;
|
||||
|
||||
if (argc > 1) { // batch mode
|
||||
std::ifstream file(argv[1]);
|
||||
|
||||
while (std::getline (file, line, '\n')) {
|
||||
s.parse(line).execute();
|
||||
}
|
||||
}
|
||||
else { // interactive mode
|
||||
std::cout << "Snel. A quick and dirty shell implementation for auth.gr" << std::endl;
|
||||
do {
|
||||
std::cout << "Choutouridis_8997>";
|
||||
std::getline (std::cin, line, '\n');
|
||||
if (line == "quit")
|
||||
break;
|
||||
s.parse(line).execute();
|
||||
} while (1);
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
exit(1);
|
||||
}
|
||||
|
197
src/sequencer.cpp
Executable file
197
src/sequencer.cpp
Executable file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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;
|
||||
if (t == "&&") logic = LogicOp::AND;
|
||||
else if (t == "||") logic = LogicOp::OR;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
112
src/sequencer.h
Executable file
112
src/sequencer.h
Executable file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* child.h
|
||||
*
|
||||
* Created on: Feb 11, 2019
|
||||
* Author: hoo2
|
||||
*/
|
||||
|
||||
#ifndef __sequencer_h__
|
||||
#define __sequencer_h__
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
|
||||
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;
|
||||
|
||||
/*!
|
||||
*
|
||||
*/
|
||||
struct ArgList {
|
||||
using type = char; // Basic data type
|
||||
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::basic_string<type>& item);
|
||||
vtype front () { return args_.front(); }
|
||||
size_t size () { return args_.size(); }
|
||||
|
||||
vtype_ptr data() noexcept {
|
||||
return args_.data();
|
||||
}
|
||||
vtype_ptr operator*() noexcept {
|
||||
return data();
|
||||
}
|
||||
private:
|
||||
std::vector<vtype> args_ {nullptr};
|
||||
};
|
||||
|
||||
|
||||
class Child {
|
||||
public:
|
||||
enum class LogicOp { NONE=0, OR, AND };
|
||||
using command_t = std::vector<std::string>;
|
||||
using Error = std::runtime_error;
|
||||
|
||||
public:
|
||||
~Child ();
|
||||
Child () noexcept = default;
|
||||
Child (const command_t& c) : command{c} { }
|
||||
Child (command_t&& c) : command{std::move(c)} { }
|
||||
|
||||
Child& make_arguments ();
|
||||
bool execute ();
|
||||
private:
|
||||
void redirect_std_if(std::string fn, fd_t std_fd);
|
||||
void restore_std_if(fd_t std_fd);
|
||||
private:
|
||||
command_t command {};
|
||||
ArgList arguments {};
|
||||
|
||||
fd_t files[3] = {-1, -1, -1};
|
||||
fd_t sstd [3] = {-1, -1, -1};
|
||||
std::string filenames[3] = {"", "", ""};
|
||||
LogicOp logic {LogicOp::NONE};
|
||||
bool pipe {false};
|
||||
};
|
||||
|
||||
|
||||
class Sequencer {
|
||||
public:
|
||||
Sequencer& parse (const std::string& input);
|
||||
Sequencer& execute ();
|
||||
|
||||
private:
|
||||
bool is_seperator (std::string& s) {
|
||||
return (s == "&&" || s == "||") ? true : false;
|
||||
}
|
||||
bool is_pipe (std::string& s) {
|
||||
return (s == "|") ? true : false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector <
|
||||
std::vector <Child>
|
||||
> seq_ {};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* __sequencer_h__ */
|
Loading…
x
Reference in New Issue
Block a user