PageRenderTime 26ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/src/nova/process.h

https://github.com/TimSimpsonR/sneaky-pete
C Header | 414 lines | 214 code | 116 blank | 84 comment | 4 complexity | b24ed9fc8bc7e1d7ab8f52942247a97c MD5 | raw file
  1. #ifndef __NOVA_PROCESS_H
  2. #define __NOVA_PROCESS_H
  3. #include "nova/utils/io.h"
  4. #include <boost/optional.hpp>
  5. #include "nova/Log.h"
  6. #include <list>
  7. #include <sstream>
  8. #include <boost/utility.hpp>
  9. #include <vector>
  10. /**
  11. * This namespace allows you to create and manipulate processes using the
  12. * Process type.
  13. *
  14. * Processes start when the constructor runs and continue until "wait_for_exit"
  15. * or "wait_forever_for_exit" is called. At this point (and no earlier)
  16. * "is_finished()" will return true and "successful()" will return true if
  17. * the process ended normally and the exit code was zero.
  18. *
  19. * The Process class is actually a template which can take an empty type
  20. * parameter list. If you need to read or write to a process's standard
  21. * streams, the following classes can be added to the template argument list
  22. * (they can be thought of as mix-ins):
  23. *
  24. * StdIn - This adds methods to read from STDIN.
  25. *
  26. * StdErrAndStdOut - This treats a process's STDERR and STDOUT stream as a
  27. * single, unified stream which can be read from.
  28. *
  29. * This example calls ls and reads the current directories. If ls takes
  30. * longer than 60 seconds to time out, it throws a TimeOutException:
  31. *
  32. * std::stringstream buffer;
  33. * CommandList cmds = list_of("ls");
  34. * Process<StdErrAndStdOut> proc(cmds);
  35. * proc.read_into_until_exit(buffer, 60);
  36. * std::vector<std::string> files;
  37. * while (buffer.good()) {
  38. * std::string file << buffer;
  39. * files.push_back();
  40. * }
  41. */
  42. namespace nova { namespace process {
  43. /** Simple list of commands. */
  44. typedef std::list<std::string> CommandList;
  45. /** Executes the given command, waiting until its finished. Throws an
  46. * error if the exit code is not zero. */
  47. void execute(const CommandList & cmds, boost::optional<double> time_out=30);
  48. /** Like the corresponding "execute" command but pipes stdout / stderr to
  49. * a stream. Some processes need this to function correctly! */
  50. void execute_with_stdout_and_stderr(const CommandList & cmds,
  51. boost::optional<double> time_out=30,
  52. bool check_proc=true);
  53. /** Like the corresponding "execute" command but only pipes stdout
  54. * a stream. Some processes need this to function correctly! */
  55. void execute_with_stdout_only(const CommandList & cmds,
  56. boost::optional<double> time_out=30,
  57. bool check_proc=true);
  58. /** Similar to execute, but throws a TimeOutException if any reads take
  59. * longer than the time_out argument. */
  60. void execute(std::stringstream & out, const CommandList & cmds,
  61. double time_out=30);
  62. /** Executes the given command but does not wait until its finished.
  63. * This function is an anomaly because unlike most functions of this
  64. * class it does not open up the process's streams or wait for it. */
  65. pid_t execute_and_abandon(const CommandList & cmds);
  66. /* Terminates a program with maximum prejudice. */
  67. void force_kill(pid_t pid);
  68. /** Returns true if the given pid is alive. */
  69. bool is_pid_alive(pid_t pid);
  70. /** Uses the system call. Throws exception if exit code is not equal to 0. */
  71. void shell(const char * const cmds, bool log = true);
  72. class ProcessException : public std::exception {
  73. public:
  74. enum Code {
  75. EXIT_CODE_NOT_ZERO,
  76. GENERAL,
  77. KILL_SIGNAL_ERROR,
  78. NO_PROGRAM_GIVEN,
  79. PROGRAM_FINISHED,
  80. SHELL_EXIT_CODE_NOT_ZERO,
  81. SPAWN_FAILURE
  82. };
  83. ProcessException(Code code) throw();
  84. virtual ~ProcessException() throw();
  85. const Code code;
  86. virtual const char * what() const throw();
  87. };
  88. class SpawnFileActions;
  89. /** Holds onto a pid ID and monitors it, grabbing the exit code when it dies. */
  90. class ProcessStatusWatcher : private boost::noncopyable {
  91. public:
  92. /** Creates an object which will get the status of pid. If
  93. * "wait_for_close" is true, the program will hang until the process
  94. * identified by pid dies. */
  95. ProcessStatusWatcher();
  96. ~ProcessStatusWatcher();
  97. inline pid_t & get_pid() {
  98. return pid;
  99. }
  100. inline const pid_t & get_pid() const {
  101. return pid;
  102. }
  103. /** True if the process has ended. */
  104. inline bool is_finished() const {
  105. return finished_flag;
  106. }
  107. /** This is always false until eof() returns true. */
  108. inline bool successful() const {
  109. return success;
  110. }
  111. /** Called when you're ready to get the processes exit status.
  112. * This needs to be when you're finished with the process and
  113. * perceive it to have completed.*/
  114. void wait_for_exit_code(bool wait_forever);
  115. private:
  116. bool finished_flag;
  117. pid_t pid;
  118. bool success;
  119. int call_waitpid(int * status, bool do_not_wait=false);
  120. };
  121. class ProcessFileHandler;
  122. class ProcessBase : private boost::noncopyable {
  123. public:
  124. ~ProcessBase();
  125. inline bool is_finished() const {
  126. return status_watcher.is_finished();
  127. }
  128. inline pid_t & get_pid() {
  129. return status_watcher.get_pid();
  130. }
  131. void kill(double initial_wait_time,
  132. boost::optional<double> serious_wait_time);
  133. inline bool successful() const {
  134. return status_watcher.successful();
  135. }
  136. /** Waits for the process to exit so it can retrieve it's exit code.
  137. * Throws TimeOutException if it doesn't happen. */
  138. void wait_for_exit(double seconds);
  139. /** Waits for the process to exit. Does not time out.
  140. * Does not collect stdout. */
  141. void wait_forever_for_exit();
  142. protected:
  143. ProcessBase();
  144. void add_io_handler(ProcessFileHandler * handler);
  145. void destroy();
  146. void drain_io_from_file_handlers(boost::optional<double> seconds);
  147. void initialize(const CommandList & cmds);
  148. virtual void pre_spawn_stderr_actions(SpawnFileActions & sp);
  149. virtual void pre_spawn_stdin_actions(SpawnFileActions & sp);
  150. virtual void pre_spawn_stdout_actions(SpawnFileActions & sp);
  151. private:
  152. std::list<ProcessFileHandler *> io_watchers;
  153. ProcessStatusWatcher status_watcher;
  154. void _wait_for_exit_code(bool wait_forever);
  155. };
  156. /* This class is responsible for calling a process, and allows ProcessIO
  157. * instances to interact with the process's file handles. To do this it demands
  158. * that they give it a shared pointer to a ProcessFileHandler class (see below).
  159. */
  160. template<typename... IoClasses>
  161. class Process : public virtual ProcessBase, public IoClasses... {
  162. public:
  163. Process(const CommandList & cmds) {
  164. initialize(cmds);
  165. }
  166. ~Process() {
  167. destroy();
  168. }
  169. };
  170. class ProcessFileHandler {
  171. friend class ProcessBase;
  172. protected:
  173. virtual ~ProcessFileHandler() {}
  174. virtual void drain_io(boost::optional<double> seconds) {};
  175. virtual void post_spawn_actions() = 0;
  176. virtual void set_eof_actions() {};
  177. };
  178. class StdIn : public ProcessFileHandler, public virtual ProcessBase {
  179. public:
  180. StdIn();
  181. virtual ~StdIn();
  182. /** Writes to the process's standard input. */
  183. void write(const char * msg);
  184. /** Writes to the process's standard input. */
  185. void write(const char * msg, size_t length);
  186. protected:
  187. virtual void post_spawn_actions();
  188. virtual void pre_spawn_stdin_actions(SpawnFileActions & sp);
  189. virtual void set_eof_actions();
  190. private:
  191. nova::utils::io::Pipe std_in_pipe;
  192. };
  193. class StdErrToFile : public ProcessFileHandler, public virtual ProcessBase {
  194. public:
  195. StdErrToFile();
  196. virtual ~StdErrToFile();
  197. virtual const char * log_file_name() = 0;
  198. protected:
  199. virtual void post_spawn_actions();
  200. virtual void pre_spawn_stderr_actions(SpawnFileActions & sp);
  201. private:
  202. int file_descriptor;
  203. };
  204. class IndependentStdErrAndStdOut : public ProcessFileHandler,
  205. public virtual ProcessBase {
  206. public:
  207. struct ReadResult {
  208. enum FileIndex {
  209. StdErr = 2,
  210. StdOut = 1,
  211. Eof = 0,
  212. TimeOut = 3
  213. };
  214. inline bool err() const {
  215. return file == StdErr;
  216. }
  217. FileIndex file;
  218. inline bool eof() const {
  219. return file == Eof;
  220. }
  221. inline bool out() const {
  222. return file == StdOut;
  223. }
  224. inline bool time_out() const {
  225. return file == TimeOut;
  226. }
  227. size_t write_length;
  228. };
  229. IndependentStdErrAndStdOut();
  230. virtual ~IndependentStdErrAndStdOut();
  231. /* Reads from either stdour or stderr, and returns the bytes read
  232. * as well as from which stream reading occurred. */
  233. ReadResult read_into(std::stringstream & std_out,
  234. const boost::optional<double> seconds);
  235. ReadResult read_into(char * buffer, const size_t length,
  236. boost::optional<double> seconds);
  237. bool std_err_closed() const {
  238. return !std_err_pipe.in_is_open();
  239. }
  240. bool std_out_closed() const {
  241. return !std_out_pipe.in_is_open();
  242. }
  243. protected:
  244. virtual void drain_io(boost::optional<double> seconds);
  245. virtual void post_spawn_actions();
  246. virtual void pre_spawn_stderr_actions(SpawnFileActions & sp);
  247. virtual void pre_spawn_stdout_actions(SpawnFileActions & sp);
  248. // Reads from the last remaing stream.
  249. ReadResult _read_into(char * buffer, const size_t length,
  250. const boost::optional<double> seconds);
  251. virtual void set_eof_actions();
  252. private:
  253. bool draining;
  254. nova::utils::io::Pipe std_err_pipe;
  255. nova::utils::io::Pipe std_out_pipe;
  256. };
  257. class StdErrAndStdOut : public ProcessFileHandler, public virtual ProcessBase {
  258. public:
  259. StdErrAndStdOut();
  260. virtual ~StdErrAndStdOut();
  261. /* Waits until the process's stdout stream has bytes to read or the
  262. * number of seconds specified by the argument "seconds" passes.
  263. * If seconds is not set will block here forever (unless a Timer is
  264. * used).
  265. * Writes any bytes read to the given argument stream.
  266. * Returns the number of bytes read (0 for time out).If end of file
  267. * is encountered the eof property is set to true. */
  268. size_t read_into(std::stringstream & std_out,
  269. const boost::optional<double> seconds=boost::none);
  270. size_t read_into(char * buffer, const size_t length,
  271. const boost::optional<double> seconds=boost::none);
  272. /** Waits for EOF while reading into the given stream. Note that this
  273. * can result in a very large stream as all the standard output is
  274. * captured!
  275. * Throws TimeOutException if it doesn't happen. */
  276. void read_into_until_exit(std::stringstream & out, double seconds);
  277. /* Reads from the process's stdout into the string stream until
  278. * stdout does not have any data for the given number of seconds.
  279. * Returns the number of bytes read. If end of file is encountered,
  280. * sets the eof property to true. */
  281. size_t read_until_pause(std::stringstream & std_out,
  282. const double time_out);
  283. protected:
  284. virtual void drain_io(boost::optional<double> seconds);
  285. virtual void post_spawn_actions();
  286. virtual void pre_spawn_stderr_actions(SpawnFileActions & sp);
  287. virtual void pre_spawn_stdout_actions(SpawnFileActions & sp);
  288. virtual void set_eof_actions();
  289. private:
  290. bool draining;
  291. nova::utils::io::Pipe std_out_pipe;
  292. };
  293. class StdOutOnly : public virtual StdErrAndStdOut {
  294. protected:
  295. virtual void pre_spawn_stderr_actions(SpawnFileActions & sp);
  296. };
  297. } } // end nova::process namespace
  298. #endif