A quick and dirty shell implementation for A.U.TH. (Operating systems Lab)
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 

359 Zeilen
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. }