Snel
A shell implementation for A.U.TH (Operating Systems Lab)
sequencer.cpp
Go to the documentation of this file.
1 
10 #include "sequencer.h"
11 
12 namespace snel {
13 
15  template <typename T>
16  void memcpy (T* to, const T* from, size_t n) {
17  for (size_t i=0 ; i<n ; ++i)
18  *to++ = *from++;
19  }
20 
30  template <typename T, typename Container>
31  void split(const std::basic_string<T>& str, Container& cont, T delim) {
32  std::basic_stringstream<T> ss(str);
33  std::basic_string<T> token;
34  while (std::getline(ss, token, delim)) {
35  cont.push_back(token);
36  }
37  }
38 
49  std::string filter (const std::string in) {
50  auto first = in.find_first_not_of(' ');
51  std::string nonws = (first != std::string::npos) ?
52  in.substr(first) : "";
53  std::string out{};
54  char prev {0}, cur, next {0};
55 
56  // return empty if the first non-whitespace character is '#'
57  if (nonws[0] == '#')
58  return out;
59 
60  for (auto i = nonws.begin(); i != nonws.end() ; ++i) {
61  cur = *i;
62  if (cur == '|') {
63  if (prev != ' ') out += ' '; // prev| insert a space before
64  next = *++i;
65  if (next == '|' || next == ' ') {
66  out += cur; cur = next; // syntax ok
67  }
68  else {
69  out += cur; out += ' '; // |next, insert a space after
70  cur = next;
71  }
72  }
73  prev = cur;
74  out += cur;
75  }
76  return out;
77  }
78 
79 
80  /*
81  * ========== ArgList ==========
82  */
89  for (vtype p : args_)
90  if (p != nullptr) delete[] p;
91  }
92 
99  ArgList& ArgList::push_back(const std::string& item) {
100  vtype it = new type[item.length()+1];
101 
102  memcpy (it, item.c_str(), item.length()); // get data and null terminate them
103  it[item.length()] = 0;
104 
105  args_.back() = it; // update the argument vector
106  args_.push_back(nullptr);
107  return *this;
108  }
109 
110 
111  /*
112  * ========== Pipe ===========
113  */
116  // close any possibly leaked pipes
117  if (fd[0] != -1) close(fd[0]);
118  if (fd[1] != -1) close(fd[1]);
119  }
120 
121  /*
122  * ========== Child ==========
123  */
124 
127  for (int i=0 ; i<3 ; ++i)
128  restore_std_if(i);
129  }
130 
136  void Child::redirect_std_if(std::string fn, fd_t std_fd) {
137  if (fn != "") {
138  if ((files[std_fd] = openat(AT_FDCWD, fn.c_str(), O_RDWR | O_CREAT, 0640)) == -1)
139  throw Error ("Child: Can not open file");
140  if ((sstd[std_fd] = dup (std_fd)) == -1) // save STDxxx
141  throw Error ("Child: Can not create file descriptor");
142  if ((dup2(files[std_fd], std_fd)) == -1) // use input as STDIN_
143  throw Error ("Child: Can not redirect file descriptor");
144  }
145  }
151  if (sstd[std_fd] != -1) {
152  dup2(sstd[std_fd], std_fd);
153  close (sstd[std_fd]);
154  sstd[std_fd] = -1;
155  }
156  if (files[std_fd] != -1) {
157  close (files[std_fd]);
158  files[std_fd] = -1;
159  }
160 
161  }
162 
168  bool in{false}, out{false}, err{false};
169  bool CanRedirectIn = (!pipe_.from) ? true : false;
170  bool CanRedirectOut = (!pipe_.to) ? true : false;
171 
172  for (auto& t: command) {
173  if (t == "" || t== " " || t=="|") continue; // skip crap
174 
175  if (t == "&&") logic = LogicOp::AND;
176  else if (t == "||") logic = LogicOp::OR;
177  // one pass redirection parsing
178  else if (CanRedirectIn && !t.compare(0, 1, "<"))
179  filenames[STDIN_] = t.substr(1);
180  else if (CanRedirectOut&& !t.compare(0, 1, ">"))
181  filenames[STDOUT_] = t.substr(1);
182  else if (CanRedirectOut&& !t.compare(0, 2, "1>"))
183  filenames[STDOUT_] = t.substr(2);
184  else if (!t.compare(0, 2, "2>"))
185  filenames[STDERR_] = t.substr(2);
186  // two pass redirection parsing (if redirection came in 2 arguments)
187  else if (t == "<") in = true;
188  else if (t == "1>" || t == ">") out = true;
189  else if (t == "2>") err = true;
190  else if (in) {
191  if (CanRedirectIn)
192  filenames[STDIN_] = t;
193  in = false;
194  }
195  else if (out) {
196  if (CanRedirectOut)
197  filenames[STDOUT_] = t;
198  out = false;
199  }
200  else if (err) {
201  filenames[STDERR_] = t; err = false;
202  }
203  else
204  arguments.push_back(t);
205  }
206  return *this;
207  }
208 
226  bool Child::execute(std::vector<Child>::iterator it, bool first) {
227  bool stop {false};
228  Child& previous = (!first) ? *--it : *it; // get previous child safely
229 
230  // Check redirection requirements
231  redirect_std_if (filenames[STDIN_], STDIN_);
232  redirect_std_if (filenames[STDOUT_], STDOUT_);
233  redirect_std_if (filenames[STDERR_], STDERR_);
234 
235  // Check parent pipe control
236  if (pipe_.to) {
237  if((::pipe (pipe_.fd)) == -1)
238  throw Error("Child: Can not create pipe");
239  }
240  pid_t pid = fork();
241  if (pid < 0) {
242  throw Error("Child: Can not create child process");
243  }
244  else if (pid == 0) {
245  // =========== child ================
246 
247  // Some extra pipe checking while we still have our data
248  if (pipe_.to) { // transmitting child
249  close(pipe_.fd[0]); // close the read end (of the child copy)
250  if ((dup2(pipe_.fd[1], STDOUT_)) == -1) // redirect output
251  throw Error("Child pipe[to]: Can not redirect through pipe");
252  }
253  else if (!first && pipe_.from) { // receiving child
254  close (previous.pipe().fd[1]); // close the write end (of the child copy)
255  if ((dup2(previous.pipe().fd[0], STDIN_)) == -1) // redirect input
256  throw Error("Child pipe[from]: Can not redirect through pipe");
257  }
258 
259  execvp(arguments.front(), arguments.data());
260  // error if we got here
261  std::cout << "Can not invoke: " << arguments.front() << std::endl;
262  throw Error("Child: Can not run child process");
263  }
264  else {
265  // =========== parent ================
266  if (pipe_.to) close (pipe_.fd[1]); // Pipe fd control
267  else if (!first && pipe_.from)
268  close (previous.pipe().fd[0]);
269 
270  int exit_status;
271  waitpid(pid, &exit_status, 0); // wait child process to finish
272  restore_std_if (STDIN_); // restore all redirections
273  restore_std_if (STDOUT_);
274  restore_std_if (STDERR_);
275 
276  // Check if we don't have to execute the rest of the chain
277  if ( exit_status && logic == LogicOp::AND) stop = true;
278  if (!exit_status && logic == LogicOp::OR) stop = true;
279  }
280 
281  return stop;
282  }
283 
284 
285 
286 
287  /*
288  * ======== Sequencer ===========
289  */
290 
299  Sequencer& Sequencer::parse (const std::string& input) {
300  std::vector<std::string> chains; // chains are vector of commands
301  std::vector<std::string> tokens; // token is any ' ' separated string
302  Child::command_t command;
303 
304  split (input, chains, ';'); // split input to command chains using ';'
305  for (auto& chain : chains) { // for each chain
306  command.clear(); // clear child tokens
307  tokens.clear(); // make a new token vector for command
308  split (chain, tokens, ' '); // split chain in to tokens using ' '
309 
310  seq_.emplace_back(std::vector<Child>{}); // make a new child for command
311  // check tokens inside the chain
312  bool from = false;
313  for (auto& t: tokens) {
314  command.push_back(t); // get current token
315  if (is_seperator(t)) {
316  // construct a child with what we have so far and clear command
317  seq_.back().emplace_back(command);
318  command.clear();
319  if (from) {
320  // set from flag to link with the previous child
321  seq_.back().back().pipe().from = from;
322  from = false;
323  }
324 
325  if (is_pipe(t)) {
326  seq_.back().back().pipe().to = true; // mark as transmitting child
327  from = true; // mark to inform the next child as receiving
328  }
329  }
330  }
331  seq_.back().emplace_back(command); // construct the child with tokens
332  if (from)
333  seq_.back().back().pipe().from = from;
334  }
335  return *this;
336  }
337 
344  bool first;
345  for (auto& chain : seq_) { // loop in all of the command chains
346  first = true;
347  for (auto it = chain.begin() ; it != chain.end(); ++it) {
348  // loop all of the commands in the chain
349  Child& command = *it;
350  if (command.make_arguments().execute(it, first))
351  break; // break the chain if child's logic operator says so.
352  first = false;
353  }
354  }
355  seq_.clear(); // clear the sequencer's data for the next line (call all the destructors)
356  return *this;
357  }
358 
359 }
std::string filter(const std::string in)
Definition: sequencer.cpp:49
void restore_std_if(fd_t std_fd)
Definition: sequencer.cpp:150
bool execute(std::vector< Child >::iterator it, bool first)
Execute the child in separate process.
Definition: sequencer.cpp:226
Sequencer & parse(const std::string &input)
Definition: sequencer.cpp:299
Sequencer & execute()
Definition: sequencer.cpp:343
constexpr fd_t STDERR_
Constant for stderr file descriptor.
Definition: sequencer.h:34
~Pipe()
Destructor to free up all the resources.
Definition: sequencer.cpp:115
std::vector< vtype > args_
underling data for the execvp() arguments
Definition: sequencer.h:72
std::vector< std::string > command_t
A command type.
Definition: sequencer.h:104
~Child()
Destructor to free up all the resources.
Definition: sequencer.cpp:126
std::runtime_error Error
An error type.
Definition: sequencer.h:105
Pipe & pipe()
A pipe_ getter.
Definition: sequencer.h:115
void memcpy(T *to, const T *from, size_t n)
A type-safe memcpy.
Definition: sequencer.cpp:16
typename std::string::value_type type
Basic data type (aka char)
Definition: sequencer.h:53
A basic sequence interpreter for snel.
int fd_t
file descriptor type
Definition: sequencer.h:30
void split(const std::basic_string< T > &str, Container &cont, T delim)
Definition: sequencer.cpp:31
void redirect_std_if(std::string fn, fd_t std_fd)
Tool to redirect std.
Definition: sequencer.cpp:136
ArgList & push_back(const std::string &item)
A vector::push_back wrapper.
Definition: sequencer.cpp:99
type * vtype
Vector type.
Definition: sequencer.h:54
Child & make_arguments()
A per child parser before execution.
Definition: sequencer.cpp:167
constexpr fd_t STDIN_
Constant for stdin file descriptor.
Definition: sequencer.h:32
constexpr fd_t STDOUT_
Constant for stdout file descriptor.
Definition: sequencer.h:33