A quick and dirty shell implementation for A.U.TH. (Operating systems Lab)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

359 lines
12 KiB

  1. /*!
  2. * \file
  3. * Sequencer.cpp
  4. * \brief A basic sequence interpreter for snel
  5. *
  6. * Created on: Feb, 2019
  7. * Author: Christos Choutouridis AEM: 8997
  8. * email : cchoutou@ece.auth.gr
  9. */
  10. #include "sequencer.h"
  11. namespace snel {
  12. //! A type-safe memcpy
  13. template <typename T>
  14. void memcpy (T* to, const T* from, size_t n) {
  15. for (size_t i=0 ; i<n ; ++i)
  16. *to++ = *from++;
  17. }
  18. /*!
  19. * Split a string to tokens and store them in a container, using a delimiter
  20. * character.
  21. * \note
  22. * Requires: container with push_back functionality
  23. * \param str The input string to split
  24. * \param cont The container to push the tokens (MUST have .push_back(T) member)
  25. * \param delim The delimiter of type \p T to use
  26. */
  27. template <typename T, typename Container>
  28. void split(const std::basic_string<T>& str, Container& cont, T delim) {
  29. std::basic_stringstream<T> ss(str);
  30. std::basic_string<T> token;
  31. while (std::getline(ss, token, delim)) {
  32. cont.push_back(token);
  33. }
  34. }
  35. /*!
  36. * A very very simple filtering for leading ' ', comments and adjustments for
  37. * the '|' character.
  38. * \note
  39. * The current snel implementation requires '|' to separated
  40. * from left and right from the rest of the text, so we adjust the input to fit
  41. * this expectation.
  42. * \param in Input string
  43. * \return Output string
  44. */
  45. std::string filter (const std::string in) {
  46. auto first = in.find_first_not_of(' ');
  47. std::string nonws = (first != std::string::npos) ?
  48. in.substr(first) : "";
  49. std::string out{};
  50. char prev {0}, cur, next {0};
  51. // return empty if the first non-whitespace character is '#'
  52. if (nonws[0] == '#')
  53. return out;
  54. for (auto i = nonws.begin(); i != nonws.end() ; ++i) {
  55. cur = *i;
  56. if (cur == '|') {
  57. if (prev != ' ') out += ' '; // prev| insert a space before
  58. next = *++i;
  59. if (next == '|' || next == ' ') {
  60. out += cur; cur = next; // syntax ok
  61. }
  62. else {
  63. out += cur; out += ' '; // |next, insert a space after
  64. cur = next;
  65. }
  66. }
  67. prev = cur;
  68. out += cur;
  69. }
  70. return out;
  71. }
  72. /*
  73. * ========== ArgList ==========
  74. */
  75. /*!
  76. * destructor to free up all the allocations
  77. * \note
  78. * Using RAII for ArgList we ensure "no memory leak"
  79. */
  80. ArgList::~ArgList() {
  81. for (vtype p : args_)
  82. if (p != nullptr) delete[] p;
  83. }
  84. /*!
  85. * A push_back wrapper to our underling vector. This ensures NULL terminating
  86. * argument list for exec() sys-call.
  87. * \param item The argument to push
  88. * \return Reference to ArgList for chaining
  89. */
  90. ArgList& ArgList::push_back(const std::string& item) {
  91. vtype it = new type[item.length()+1];
  92. memcpy (it, item.c_str(), item.length()); // get data and null terminate them
  93. it[item.length()] = 0;
  94. args_.back() = it; // update the argument vector
  95. args_.push_back(nullptr);
  96. return *this;
  97. }
  98. /*
  99. * ========== Pipe ===========
  100. */
  101. //! Destructor to free up all the resources
  102. Pipe::~Pipe() {
  103. // close any possibly leaked pipes
  104. if (fd[0] != -1) close(fd[0]);
  105. if (fd[1] != -1) close(fd[1]);
  106. }
  107. /*
  108. * ========== Child ==========
  109. */
  110. //! Destructor to free up all the resources
  111. Child::~Child () {
  112. for (int i=0 ; i<3 ; ++i)
  113. restore_std_if(i);
  114. }
  115. /*!
  116. * Redirect a \p std_fd file descriptor to a file
  117. * @param fn The file name to used as std
  118. * @param std_fd The file descriptor to redirect
  119. */
  120. void Child::redirect_std_if(std::string fn, fd_t std_fd) {
  121. if (fn != "") {
  122. if ((files[std_fd] = openat(AT_FDCWD, fn.c_str(), O_RDWR | O_CREAT, 0640)) == -1)
  123. throw Error ("Child: Can not open file");
  124. if ((sstd[std_fd] = dup (std_fd)) == -1) // save STDxxx
  125. throw Error ("Child: Can not create file descriptor");
  126. if ((dup2(files[std_fd], std_fd)) == -1) // use input as STDIN_
  127. throw Error ("Child: Can not redirect file descriptor");
  128. }
  129. }
  130. /*!
  131. * Restore and redirected std file descriptor to its original location
  132. * \param std_fd The file descriptor to restore
  133. */
  134. void Child::restore_std_if(fd_t std_fd) {
  135. if (sstd[std_fd] != -1) {
  136. dup2(sstd[std_fd], std_fd);
  137. close (sstd[std_fd]);
  138. sstd[std_fd] = -1;
  139. }
  140. if (files[std_fd] != -1) {
  141. close (files[std_fd]);
  142. files[std_fd] = -1;
  143. }
  144. }
  145. /*!
  146. * Parse the command of the current child to produce the actual arguments
  147. * \return Reference to Child for chaining
  148. */
  149. Child& Child::make_arguments () {
  150. bool in{false}, out{false}, err{false};
  151. bool CanRedirectIn = (!pipe_.from) ? true : false;
  152. bool CanRedirectOut = (!pipe_.to) ? true : false;
  153. for (auto& t: command) {
  154. if (t == "" || t== " " || t=="|") continue; // skip crap
  155. if (t == "&&") logic = LogicOp::AND;
  156. else if (t == "||") logic = LogicOp::OR;
  157. // one pass redirection parsing
  158. else if (CanRedirectIn && !t.compare(0, 1, "<"))
  159. filenames[STDIN_] = t.substr(1);
  160. else if (CanRedirectOut&& !t.compare(0, 1, ">"))
  161. filenames[STDOUT_] = t.substr(1);
  162. else if (CanRedirectOut&& !t.compare(0, 2, "1>"))
  163. filenames[STDOUT_] = t.substr(2);
  164. else if (!t.compare(0, 2, "2>"))
  165. filenames[STDERR_] = t.substr(2);
  166. // two pass redirection parsing (if redirection came in 2 arguments)
  167. else if (t == "<") in = true;
  168. else if (t == "1>" || t == ">") out = true;
  169. else if (t == "2>") err = true;
  170. else if (in) {
  171. if (CanRedirectIn)
  172. filenames[STDIN_] = t;
  173. in = false;
  174. }
  175. else if (out) {
  176. if (CanRedirectOut)
  177. filenames[STDOUT_] = t;
  178. out = false;
  179. }
  180. else if (err) {
  181. filenames[STDERR_] = t; err = false;
  182. }
  183. else
  184. arguments.push_back(t);
  185. }
  186. return *this;
  187. }
  188. /*!
  189. * \brief Execute the child in separate process.
  190. *
  191. * This is the hart of the snel. We use fork() - execvp() pair to execute
  192. * each child. In case of std redirection, the fd/dup2 handling is made in the
  193. * parent. In case of piping. The fd handling is made in parent and the
  194. * dup2() calls in the child process.
  195. *
  196. * A child is a member of a vector containing the command chain.
  197. * For ex: `ls && cat tralala || uname -a` is a chain with three
  198. * childs (ls, cat, uname). See also \see Sequencer
  199. *
  200. * \param it Iterator to the command chain execution
  201. * \param first flag to indicate the first command in the chain
  202. * \return True if the chain can stop.
  203. */
  204. bool Child::execute(std::vector<Child>::iterator it, bool first) {
  205. bool stop {false};
  206. Child& previous = (!first) ? *--it : *it; // get previous child safely
  207. // Check redirection requirements
  208. redirect_std_if (filenames[STDIN_], STDIN_);
  209. redirect_std_if (filenames[STDOUT_], STDOUT_);
  210. redirect_std_if (filenames[STDERR_], STDERR_);
  211. // Check parent pipe control
  212. if (pipe_.to) {
  213. if((::pipe (pipe_.fd)) == -1)
  214. throw Error("Child: Can not create pipe");
  215. }
  216. pid_t pid = fork();
  217. if (pid < 0) {
  218. throw Error("Child: Can not create child process");
  219. }
  220. else if (pid == 0) {
  221. // =========== child ================
  222. // Some extra pipe checking while we still have our data
  223. if (pipe_.to) { // transmitting child
  224. close(pipe_.fd[0]); // close the read end (of the child copy)
  225. if ((dup2(pipe_.fd[1], STDOUT_)) == -1) // redirect output
  226. throw Error("Child pipe[to]: Can not redirect through pipe");
  227. }
  228. else if (!first && pipe_.from) { // receiving child
  229. close (previous.pipe().fd[1]); // close the write end (of the child copy)
  230. if ((dup2(previous.pipe().fd[0], STDIN_)) == -1) // redirect input
  231. throw Error("Child pipe[from]: Can not redirect through pipe");
  232. }
  233. execvp(arguments.front(), arguments.data());
  234. // error if we got here
  235. std::cout << "Can not invoke: " << arguments.front() << std::endl;
  236. throw Error("Child: Can not run child process");
  237. }
  238. else {
  239. // =========== parent ================
  240. if (pipe_.to) close (pipe_.fd[1]); // Pipe fd control
  241. else if (!first && pipe_.from)
  242. close (previous.pipe().fd[0]);
  243. int exit_status;
  244. waitpid(pid, &exit_status, 0); // wait child process to finish
  245. restore_std_if (STDIN_); // restore all redirections
  246. restore_std_if (STDOUT_);
  247. restore_std_if (STDERR_);
  248. // Check if we don't have to execute the rest of the chain
  249. if ( exit_status && logic == LogicOp::AND) stop = true;
  250. if (!exit_status && logic == LogicOp::OR) stop = true;
  251. }
  252. return stop;
  253. }
  254. /*
  255. * ======== Sequencer ===========
  256. */
  257. /*!
  258. * First parsing of each line. We split input in ';' to create
  259. * command chains and split each chain further to create child tokens.
  260. * These token will be processed further by the child's `make_arguments()`
  261. *
  262. * \param input The input string to parse
  263. * \return Reference to Sequencer for chaining
  264. */
  265. Sequencer& Sequencer::parse (const std::string& input) {
  266. std::vector<std::string> chains; // chains are vector of commands
  267. std::vector<std::string> tokens; // token is any ' ' separated string
  268. Child::command_t command;
  269. split (input, chains, ';'); // split input to command chains using ';'
  270. for (auto& chain : chains) { // for each chain
  271. command.clear(); // clear child tokens
  272. tokens.clear(); // make a new token vector for command
  273. split (chain, tokens, ' '); // split chain in to tokens using ' '
  274. seq_.emplace_back(std::vector<Child>{}); // make a new child for command
  275. // check tokens inside the chain
  276. bool from = false;
  277. for (auto& t: tokens) {
  278. command.push_back(t); // get current token
  279. if (is_seperator(t)) {
  280. // construct a child with what we have so far and clear command
  281. seq_.back().emplace_back(command);
  282. command.clear();
  283. if (from) {
  284. // set from flag to link with the previous child
  285. seq_.back().back().pipe().from = from;
  286. from = false;
  287. }
  288. if (is_pipe(t)) {
  289. seq_.back().back().pipe().to = true; // mark as transmitting child
  290. from = true; // mark to inform the next child as receiving
  291. }
  292. }
  293. }
  294. seq_.back().emplace_back(command); // construct the child with tokens
  295. if (from)
  296. seq_.back().back().pipe().from = from;
  297. }
  298. return *this;
  299. }
  300. /*!
  301. * The main sequencer execution. We recurse for each command chain
  302. * and each child in the chain and we parse-execute the child
  303. * \return Reference to Sequence for chaining
  304. */
  305. Sequencer& Sequencer::execute() {
  306. bool first;
  307. for (auto& chain : seq_) { // loop in all of the command chains
  308. first = true;
  309. for (auto it = chain.begin() ; it != chain.end(); ++it) {
  310. // loop all of the commands in the chain
  311. Child& command = *it;
  312. if (command.make_arguments().execute(it, first))
  313. break; // break the chain if child's logic operator says so.
  314. first = false;
  315. }
  316. }
  317. seq_.clear(); // clear the sequencer's data for the next line (call all the destructors)
  318. return *this;
  319. }
  320. }