A quick and dirty shell implementation for A.U.TH. (Operating systems Lab)
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

360 lignes
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 <tt>fork() - execvp()</tt> 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:
  198. * \code ls && cat tralala || uname -a \endcode
  199. * is a chain with three childs (ls, cat, uname). \see Sequencer
  200. *
  201. * \param it Iterator to the command chain execution
  202. * \param first flag to indicate the first command in the chain
  203. * \return True if the chain can stop.
  204. */
  205. bool Child::execute(std::vector<Child>::iterator it, bool first) {
  206. bool stop {false};
  207. Child& previous = (!first) ? *--it : *it; // get previous child safely
  208. // Check redirection requirements
  209. redirect_std_if (filenames[STDIN_], STDIN_);
  210. redirect_std_if (filenames[STDOUT_], STDOUT_);
  211. redirect_std_if (filenames[STDERR_], STDERR_);
  212. // Check parent pipe control
  213. if (pipe_.to) {
  214. if((::pipe (pipe_.fd)) == -1)
  215. throw Error("Child: Can not create pipe");
  216. }
  217. pid_t pid = fork();
  218. if (pid < 0) {
  219. throw Error("Child: Can not create child process");
  220. }
  221. else if (pid == 0) {
  222. // =========== child ================
  223. // Some extra pipe checking while we still have our data
  224. if (pipe_.to) { // transmitting child
  225. close(pipe_.fd[0]); // close the read end (of the child copy)
  226. if ((dup2(pipe_.fd[1], STDOUT_)) == -1) // redirect output
  227. throw Error("Child pipe[to]: Can not redirect through pipe");
  228. }
  229. else if (!first && pipe_.from) { // receiving child
  230. close (previous.pipe().fd[1]); // close the write end (of the child copy)
  231. if ((dup2(previous.pipe().fd[0], STDIN_)) == -1) // redirect input
  232. throw Error("Child pipe[from]: Can not redirect through pipe");
  233. }
  234. execvp(arguments.front(), arguments.data());
  235. // error if we got here
  236. std::cout << "Can not invoke: " << arguments.front() << std::endl;
  237. throw Error("Child: Can not run child process");
  238. }
  239. else {
  240. // =========== parent ================
  241. if (pipe_.to) close (pipe_.fd[1]); // Pipe fd control
  242. else if (!first && pipe_.from)
  243. close (previous.pipe().fd[0]);
  244. int exit_status;
  245. waitpid(pid, &exit_status, 0); // wait child process to finish
  246. restore_std_if (STDIN_); // restore all redirections
  247. restore_std_if (STDOUT_);
  248. restore_std_if (STDERR_);
  249. // Check if we don't have to execute the rest of the chain
  250. if ( exit_status && logic == LogicOp::AND) stop = true;
  251. if (!exit_status && logic == LogicOp::OR) stop = true;
  252. }
  253. return stop;
  254. }
  255. /*
  256. * ======== Sequencer ===========
  257. */
  258. /*!
  259. * First parsing of each line. We split input in ';' to create
  260. * command chains and split each chain further to create child tokens.
  261. * These token will be processed further by the child's `make_arguments()`
  262. *
  263. * \param input The input string to parse
  264. * \return Reference to Sequencer for chaining
  265. */
  266. Sequencer& Sequencer::parse (const std::string& input) {
  267. std::vector<std::string> chains; // chains are vector of commands
  268. std::vector<std::string> tokens; // token is any ' ' separated string
  269. Child::command_t command;
  270. split (input, chains, ';'); // split input to command chains using ';'
  271. for (auto& chain : chains) { // for each chain
  272. command.clear(); // clear child tokens
  273. tokens.clear(); // make a new token vector for command
  274. split (chain, tokens, ' '); // split chain in to tokens using ' '
  275. seq_.emplace_back(std::vector<Child>{}); // make a new child for command
  276. // check tokens inside the chain
  277. bool from = false;
  278. for (auto& t: tokens) {
  279. command.push_back(t); // get current token
  280. if (is_seperator(t)) {
  281. // construct a child with what we have so far and clear command
  282. seq_.back().emplace_back(command);
  283. command.clear();
  284. if (from) {
  285. // set from flag to link with the previous child
  286. seq_.back().back().pipe().from = from;
  287. from = false;
  288. }
  289. if (is_pipe(t)) {
  290. seq_.back().back().pipe().to = true; // mark as transmitting child
  291. from = true; // mark to inform the next child as receiving
  292. }
  293. }
  294. }
  295. seq_.back().emplace_back(command); // construct the child with tokens
  296. if (from)
  297. seq_.back().back().pipe().from = from;
  298. }
  299. return *this;
  300. }
  301. /*!
  302. * The main sequencer execution. We recurse for each command chain
  303. * and each child in the chain and we parse-execute the child
  304. * \return Reference to Sequence for chaining
  305. */
  306. Sequencer& Sequencer::execute() {
  307. bool first;
  308. for (auto& chain : seq_) { // loop in all of the command chains
  309. first = true;
  310. for (auto it = chain.begin() ; it != chain.end(); ++it) {
  311. // loop all of the commands in the chain
  312. Child& command = *it;
  313. if (command.make_arguments().execute(it, first))
  314. break; // break the chain if child's logic operator says so.
  315. first = false;
  316. }
  317. }
  318. seq_.clear(); // clear the sequencer's data for the next line (call all the destructors)
  319. return *this;
  320. }
  321. }