PageRenderTime 59ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/src/nova/process.cc

https://github.com/TimSimpsonR/sneaky-pete
C++ | 815 lines | 647 code | 111 blank | 57 comment | 87 complexity | fac339730fa36c52a5168e90e146264f MD5 | raw file
  1. #include "pch.hpp"
  2. #include "nova/process.h"
  3. #include "nova/Log.h"
  4. #include <errno.h>
  5. #include <fcntl.h> // Consider moving to io.cc and using there.
  6. #include <boost/foreach.hpp>
  7. #include <fstream>
  8. #include "nova/utils/io.h"
  9. #include <iostream>
  10. #include <malloc.h> // Valgrind complains if we don't use "free" below. ;_;
  11. #include <signal.h>
  12. #include <sys/select.h>
  13. #include <spawn.h>
  14. #include <sstream>
  15. #include <stdio.h>
  16. #include <stdlib.h> // exit
  17. #include <string.h>
  18. #include <time.h>
  19. #include <unistd.h>
  20. #include <sys/types.h>
  21. extern char **environ;
  22. using boost::optional;
  23. using nova::Log;
  24. using nova::utils::io::Pipe;
  25. using namespace nova::utils;
  26. using std::stringstream;
  27. using std::string;
  28. using nova::utils::io::TimeOutException;
  29. using nova::utils::io::Timer;
  30. using nova::utils::io::wait_pid_with_throw;
  31. bool time_out_occurred;
  32. namespace nova { namespace process {
  33. namespace {
  34. inline void checkEqual0(const int return_code,
  35. ProcessException::Code code = ProcessException::GENERAL) {
  36. if (return_code != 0) {
  37. NOVA_LOG_ERROR("System error : %s", strerror(errno));
  38. throw ProcessException(code);
  39. }
  40. }
  41. void kill_with_throw(pid_t pid, int sig, bool allow_not_found=false) {
  42. if (0 != ::kill(pid, sig)) {
  43. NOVA_LOG_ERROR("Couldn't send kill signal!! %s", strerror(errno));
  44. if (allow_not_found && ESRCH == errno){
  45. NOVA_LOG_INFO("errno was ESRCH and that's OK.");
  46. return;
  47. }
  48. throw ProcessException(ProcessException::KILL_SIGNAL_ERROR);
  49. }
  50. }
  51. /** Translates a CommandList to the type requried by posix_spawn. */
  52. class ArgV : private boost::noncopyable
  53. {
  54. public:
  55. ArgV(const CommandList & cmds)
  56. : new_argv_length(cmds.size() + 1),
  57. new_argv(new char * [new_argv_length])
  58. {
  59. size_t i = 0;
  60. BOOST_FOREACH(const string & cmd, cmds) {
  61. new_argv[i ++] = strndup(cmd.c_str(), cmd.size());
  62. }
  63. new_argv[new_argv_length - 1] = NULL;
  64. }
  65. ~ArgV() {
  66. for (size_t i = 0; i < new_argv_length; ++i) {
  67. ::free(new_argv[i]);
  68. }
  69. delete[] new_argv;
  70. new_argv = 0;
  71. }
  72. char * * get() {
  73. return new_argv;
  74. }
  75. private:
  76. const size_t new_argv_length;
  77. char * * new_argv;
  78. };
  79. const size_t BUFFER_SIZE = 1048;
  80. /** Waits for the given file descriptor to have more data for the given
  81. * number of seconds. */
  82. bool ready(int file_desc, const optional<double> seconds) {
  83. fd_set file_set;
  84. FD_ZERO(&file_set);
  85. FD_SET(file_desc, &file_set);
  86. return io::select_with_throw(file_desc + 1, &file_set, NULL, NULL, seconds)
  87. != 0;
  88. }
  89. /* If neither file descriptor has input by the time denoted by "seconds",
  90. * boost::none is returned. Otherwise, the filedesc which was ready is
  91. * returned. */
  92. optional<int> ready(int file_desc1, int file_desc2,
  93. const optional<double> seconds) {
  94. fd_set file_set;
  95. FD_ZERO(&file_set);
  96. FD_SET(file_desc1, &file_set);
  97. FD_SET(file_desc2, &file_set);
  98. int nfds = std::max(file_desc1, file_desc2) + 1;
  99. auto result = io::select_with_throw(nfds, &file_set, NULL,
  100. NULL, seconds);
  101. if (0 == result) {
  102. return boost::none;
  103. } else {
  104. if (FD_ISSET(file_desc1, &file_set)) {
  105. return file_desc1;
  106. } else {
  107. return file_desc2;
  108. }
  109. }
  110. }
  111. } // end anonymous namespace
  112. /**---------------------------------------------------------------------------
  113. *- SpawnFileActions
  114. *---------------------------------------------------------------------------*/
  115. /**
  116. * Wraps posix_spawn_file_actions_t to make sure the destroy method is
  117. * called.
  118. */
  119. class SpawnFileActions : private boost::noncopyable
  120. {
  121. public:
  122. SpawnFileActions() {
  123. checkEqual0(posix_spawn_file_actions_init(&file_actions));
  124. }
  125. ~SpawnFileActions() {
  126. posix_spawn_file_actions_destroy(&file_actions);
  127. }
  128. void add_close(int fd) {
  129. checkEqual0(posix_spawn_file_actions_addclose(&file_actions, fd));
  130. }
  131. void add_dup_to(int fd, int new_fd) {
  132. checkEqual0(posix_spawn_file_actions_adddup2(&file_actions,
  133. fd, new_fd));
  134. }
  135. inline posix_spawn_file_actions_t * get() {
  136. return &file_actions;
  137. }
  138. private:
  139. posix_spawn_file_actions_t file_actions;
  140. };
  141. namespace {
  142. /* This function really does everything related to "running a process",
  143. * all the rest of this junk is just for monitoring that process.
  144. * See "execute_and_abandon" for the simplest possible way to use this. */
  145. void spawn_process(const CommandList & cmds, pid_t * pid,
  146. SpawnFileActions * actions=0)
  147. {
  148. if (cmds.size() < 1) {
  149. throw ProcessException(ProcessException::NO_PROGRAM_GIVEN);
  150. }
  151. const string program_path = cmds.front();
  152. ArgV args(cmds);
  153. {
  154. stringstream str;
  155. str << "Running the following process: { ";
  156. BOOST_FOREACH(const string & cmd, cmds) {
  157. str << "'" << cmd << "'";
  158. str << " ";
  159. }
  160. str << "}";
  161. NOVA_LOG_DEBUG(str.str().c_str());
  162. }
  163. const posix_spawn_file_actions_t * file_actions = NULL;
  164. if (actions != 0) {
  165. file_actions = actions->get();
  166. }
  167. int status = posix_spawn(pid, program_path.c_str(), file_actions, NULL,
  168. args.get(), environ);
  169. if (status != 0) {
  170. throw ProcessException(ProcessException::SPAWN_FAILURE);
  171. }
  172. }
  173. } // end second anonymous namespace
  174. /**---------------------------------------------------------------------------
  175. *- Global Functions
  176. *---------------------------------------------------------------------------*/
  177. void execute(const CommandList & cmds, optional<double> time_out) {
  178. Process<> proc(cmds);
  179. if (time_out) {
  180. proc.wait_for_exit(time_out.get());
  181. } else {
  182. NOVA_LOG_INFO("Warning: Waiting forever for process to end.");
  183. proc.wait_forever_for_exit();
  184. }
  185. if (!proc.successful()) {
  186. throw ProcessException(ProcessException::EXIT_CODE_NOT_ZERO);
  187. }
  188. }
  189. void execute_with_stdout_and_stderr(const CommandList & cmds,
  190. optional<double> time_out, bool check_proc) {
  191. Process<StdErrAndStdOut> proc(cmds);
  192. if (time_out) {
  193. proc.wait_for_exit(time_out.get());
  194. } else {
  195. NOVA_LOG_INFO("Warning: Waiting forever for process to end.");
  196. proc.wait_forever_for_exit();
  197. }
  198. if (check_proc && !proc.successful()) {
  199. throw ProcessException(ProcessException::EXIT_CODE_NOT_ZERO);
  200. }
  201. }
  202. void execute_with_stdout_only(const CommandList & cmds,
  203. optional<double> time_out, bool check_proc) {
  204. Process<StdOutOnly> proc(cmds);
  205. if (time_out) {
  206. proc.wait_for_exit(time_out.get());
  207. } else {
  208. NOVA_LOG_INFO("Warning: Waiting forever for process to end.");
  209. proc.wait_forever_for_exit();
  210. }
  211. if (check_proc && !proc.successful()) {
  212. throw ProcessException(ProcessException::EXIT_CODE_NOT_ZERO);
  213. }
  214. }
  215. void execute(std::stringstream & out, const CommandList & cmds,
  216. double time_out) {
  217. Process<StdErrAndStdOut> proc(cmds); //, true);
  218. try {
  219. proc.read_into_until_exit(out, time_out);
  220. } catch(const TimeOutException & toe) {
  221. NOVA_LOG_ERROR("Timeout error occurred reading until eof.");
  222. // This is what the code used to do, but maybe it would be good to
  223. // double check that this is desired.
  224. proc.wait_forever_for_exit();
  225. throw;
  226. }
  227. proc.wait_forever_for_exit();
  228. if (!proc.successful()) {
  229. throw ProcessException(ProcessException::EXIT_CODE_NOT_ZERO);
  230. }
  231. }
  232. pid_t execute_and_abandon(const CommandList & cmds) {
  233. pid_t pid;
  234. spawn_process(cmds, &pid);
  235. return pid;
  236. }
  237. void force_kill(pid_t pid) {
  238. NOVA_LOG_INFO("Killing pid %d.", pid);
  239. try {
  240. kill_with_throw(pid, SIGTERM, true);
  241. } catch(const ProcessException & pe) {
  242. NOVA_LOG_ERROR("SIGTE")
  243. kill_with_throw(pid, SIGKILL, true);
  244. }
  245. }
  246. bool is_pid_alive(pid_t pid) {
  247. // Send the "null signal," so kill only performs error checking but does not
  248. // actually send a signal.
  249. int result = ::kill(pid, 0);
  250. if (result == EINVAL && result == EPERM) {
  251. NOVA_LOG_ERROR("Error calling kill with null signal: %s",
  252. strerror(errno));
  253. throw ProcessException(ProcessException::GENERAL);
  254. }
  255. // ESRCH means o such process found.
  256. return result == 0;
  257. }
  258. void shell(const char * const cmds, bool log) {
  259. if (log) {
  260. NOVA_LOG_INFO("shell: %s", cmds);
  261. }
  262. const auto code = system(cmds);
  263. if (0 != code) {
  264. NOVA_LOG_ERROR("Bad exit code (%d) for shell commands: %s", code, cmds);
  265. throw ProcessException(ProcessException::SHELL_EXIT_CODE_NOT_ZERO);
  266. }
  267. }
  268. /**---------------------------------------------------------------------------
  269. *- ProcessException
  270. *---------------------------------------------------------------------------*/
  271. ProcessException::ProcessException(Code code) throw()
  272. : code(code) {
  273. }
  274. ProcessException::~ProcessException() throw() {
  275. }
  276. const char * ProcessException::what() const throw() {
  277. switch(code) {
  278. case EXIT_CODE_NOT_ZERO:
  279. return "The exit code was not zero.";
  280. case NO_PROGRAM_GIVEN:
  281. return "No program to launch was given (first element was null).";
  282. case PROGRAM_FINISHED:
  283. return "Program is already finished.";
  284. case SHELL_EXIT_CODE_NOT_ZERO:
  285. return "Shell has non-zero exit code.";
  286. case SPAWN_FAILURE:
  287. return "Failed to spawn.";
  288. default:
  289. return "An error occurred.";
  290. }
  291. }
  292. /**---------------------------------------------------------------------------
  293. *- ProcessStatusWatcher
  294. *---------------------------------------------------------------------------*/
  295. ProcessStatusWatcher::ProcessStatusWatcher()
  296. : finished_flag(false), success(false)
  297. {
  298. }
  299. ProcessStatusWatcher::~ProcessStatusWatcher() {
  300. wait_for_exit_code(false); // Close pipes.
  301. }
  302. int ProcessStatusWatcher::call_waitpid(int * status, bool do_not_wait) {
  303. const int options = do_not_wait ? WNOHANG : 0 ;
  304. return wait_pid_with_throw(pid, status, options);
  305. }
  306. void ProcessStatusWatcher::wait_for_exit_code(bool wait_forever) {
  307. if (!is_finished()) {
  308. finished_flag = true;
  309. int status;
  310. int child_pid;
  311. if (!wait_forever) {
  312. // Here's the thing- WNOHANG causes waitpid to return 0 (fail)
  313. // nearly every time. So it's better to not specify it, even if
  314. // the calling code wanted it, and use a Timer to bust out if
  315. // we risk hanging forever. Then we can use WNOHANG as a last
  316. // resort.
  317. try {
  318. Timer timer(1);
  319. child_pid = call_waitpid(&status);
  320. } catch(const TimeOutException & toe) {
  321. NOVA_LOG_ERROR("Timed out calling waitpid without WNOHANG.");
  322. child_pid = call_waitpid(&status, true);
  323. }
  324. } else {
  325. child_pid = call_waitpid(&status);
  326. }
  327. #ifdef _NOVA_PROCESS_VERBOSE
  328. NOVA_LOG_TRACE("Child exited. wait_forever=%s child_pid=%d, "
  329. "pid=%d, Pid==pid=%s, "
  330. "WIFEXITED=%d, WEXITSTATUS=%d, "
  331. "WIFSIGNALED=%d, WIFSTOPPED=%d",
  332. (wait_forever ? "true" : "false"),
  333. child_pid, pid,
  334. (child_pid == pid ? "true" : "false"),
  335. WIFEXITED(status), (int) WEXITSTATUS(status),
  336. WIFSIGNALED(status), WIFSTOPPED(status));
  337. #endif
  338. success = child_pid == pid && WIFEXITED(status)
  339. && WEXITSTATUS(status) == 0;
  340. }
  341. }
  342. /**---------------------------------------------------------------------------
  343. *- ProcessBase
  344. *---------------------------------------------------------------------------*/
  345. ProcessBase::ProcessBase()
  346. : io_watchers(),
  347. status_watcher()
  348. {
  349. }
  350. ProcessBase::~ProcessBase() {
  351. }
  352. void ProcessBase::add_io_handler(ProcessFileHandler * handler) {
  353. io_watchers.push_back(handler);
  354. }
  355. void ProcessBase::destroy() {
  356. _wait_for_exit_code(false); // Close pipes.
  357. }
  358. void ProcessBase::drain_io_from_file_handlers(optional<double> seconds) {
  359. BOOST_FOREACH(ProcessFileHandler * ptr, io_watchers) {
  360. ptr->drain_io(seconds);
  361. }
  362. }
  363. void ProcessBase::initialize(const CommandList & cmds) { //, ProcessIOList io_list) {
  364. SpawnFileActions file_actions;
  365. pre_spawn_stderr_actions(file_actions);
  366. pre_spawn_stdin_actions(file_actions);
  367. pre_spawn_stdout_actions(file_actions);
  368. spawn_process(cmds, &(status_watcher.get_pid()), &file_actions);
  369. BOOST_FOREACH(ProcessFileHandler * const ptr, io_watchers) {
  370. ptr->post_spawn_actions();
  371. }
  372. }
  373. void ProcessBase::kill(double initial_wait_time,
  374. optional<double> serious_wait_time) {
  375. kill_with_throw(status_watcher.get_pid(), SIGTERM);
  376. try {
  377. wait_for_exit(5);
  378. } catch (const TimeOutException & toe) {
  379. if (!serious_wait_time) {
  380. throw;
  381. } else {
  382. NOVA_LOG_ERROR("Won't die, eh? Then its time to use our ultimate "
  383. "weapon.");
  384. kill_with_throw(status_watcher.get_pid(), SIGKILL, true);
  385. wait_for_exit(15);
  386. }
  387. }
  388. }
  389. void ProcessBase::pre_spawn_stderr_actions(SpawnFileActions & file_actions) {
  390. file_actions.add_close(STDERR_FILENO);
  391. }
  392. void ProcessBase::pre_spawn_stdin_actions(SpawnFileActions & file_actions) {
  393. file_actions.add_close(STDIN_FILENO);
  394. }
  395. void ProcessBase::pre_spawn_stdout_actions(SpawnFileActions & file_actions) {
  396. file_actions.add_close(STDOUT_FILENO);
  397. }
  398. void ProcessBase::_wait_for_exit_code(bool wait_forever) {
  399. if (!status_watcher.is_finished()) {
  400. BOOST_FOREACH(ProcessFileHandler * const ptr, io_watchers) {
  401. ptr->set_eof_actions();
  402. }
  403. status_watcher.wait_for_exit_code(wait_forever);
  404. }
  405. }
  406. void ProcessBase::wait_for_exit(double seconds) {
  407. NOVA_LOG_DEBUG("Waiting for %f seconds for EOF...", seconds);
  408. drain_io_from_file_handlers(seconds);
  409. Timer timer(seconds);
  410. wait_forever_for_exit();
  411. }
  412. void ProcessBase::wait_forever_for_exit() {
  413. NOVA_LOG_DEBUG("Waiting forever for EOF...");
  414. drain_io_from_file_handlers(boost::none);
  415. _wait_for_exit_code(true);
  416. }
  417. /**---------------------------------------------------------------------------
  418. *- IndependentStdErrAndStdOut
  419. *---------------------------------------------------------------------------*/
  420. IndependentStdErrAndStdOut::IndependentStdErrAndStdOut()
  421. : std_err_pipe(),
  422. std_out_pipe()
  423. {
  424. ::fcntl(std_out_pipe.in(), F_SETFL, O_NONBLOCK);
  425. ::fcntl(std_err_pipe.in(), F_SETFL, O_NONBLOCK);
  426. add_io_handler(this);
  427. }
  428. IndependentStdErrAndStdOut::~IndependentStdErrAndStdOut() {
  429. }
  430. void IndependentStdErrAndStdOut::drain_io(optional<double> seconds) {
  431. if (draining) {
  432. return;
  433. }
  434. NOVA_LOG_TRACE("Draining STDOUT / STDERR...");
  435. draining = true;
  436. char buffer[1024];
  437. std::unique_ptr<Timer> timer;
  438. if (seconds) {
  439. timer.reset(new Timer(seconds.get()));
  440. }
  441. ReadResult result;
  442. while((result = read_into(buffer, sizeof(buffer), seconds)).write_length
  443. != 0) {
  444. NOVA_LOG_TRACE("Draining again! %d", result.write_length);
  445. }
  446. }
  447. void IndependentStdErrAndStdOut::post_spawn_actions() {
  448. std_out_pipe.close_out();
  449. std_err_pipe.close_out();
  450. NOVA_LOG_TRACE("Closing the out side of the stderr/stdout pipe.");
  451. }
  452. void IndependentStdErrAndStdOut::pre_spawn_stderr_actions(
  453. SpawnFileActions & file_actions)
  454. {
  455. NOVA_LOG_TRACE("Opening up stderr pipes.");
  456. file_actions.add_dup_to(std_err_pipe.out(), STDERR_FILENO);
  457. file_actions.add_close(std_err_pipe.in());
  458. }
  459. void IndependentStdErrAndStdOut::pre_spawn_stdout_actions(
  460. SpawnFileActions & file_actions)
  461. {
  462. NOVA_LOG_TRACE("Opening up stdout pipes.");
  463. file_actions.add_dup_to(std_out_pipe.out(), STDOUT_FILENO);
  464. file_actions.add_close(std_out_pipe.in());
  465. }
  466. void IndependentStdErrAndStdOut::set_eof_actions() {
  467. std_out_pipe.close_in();
  468. std_err_pipe.close_in();
  469. NOVA_LOG_TRACE("Closing in side of the stderr and stdout pipes.");
  470. }
  471. IndependentStdErrAndStdOut::ReadResult IndependentStdErrAndStdOut::read_into(
  472. stringstream & std_out, const optional<double> seconds) {
  473. char buf[BUFFER_SIZE];
  474. const auto result = read_into(buf, BUFFER_SIZE-1, seconds);
  475. buf[result.write_length] = 0;
  476. std_out.write(buf, result.write_length);
  477. NOVA_LOG_TRACE("buffer output:%s", buf);
  478. NOVA_LOG_TRACE("count = %d, SO FAR %d", result.write_length, std_out.str().length());
  479. return result;
  480. }
  481. IndependentStdErrAndStdOut::ReadResult IndependentStdErrAndStdOut::read_into(
  482. char * buffer, const size_t length, const optional<double> seconds)
  483. {
  484. NOVA_LOG_TRACE("read_into with timeout=%f", !seconds ? 0.0 : seconds.get());
  485. if (!std_out_pipe.in_is_open() && !std_err_pipe.in_is_open()) {
  486. throw ProcessException(ProcessException::PROGRAM_FINISHED);
  487. }
  488. if (std_out_pipe.in_is_open() != std_err_pipe.in_is_open()) {
  489. return _read_into(buffer, length, seconds);
  490. }
  491. const auto result = ready(this->std_out_pipe.in(), this->std_err_pipe.in(),
  492. seconds);
  493. if (!result) {
  494. NOVA_LOG_TRACE("ready returned nothing. Returning TimeOut from read_into");
  495. return { ReadResult::TimeOut, 0 };
  496. }
  497. const int file_desc = result.get();
  498. const size_t count = io::read_with_throw(file_desc, buffer, length);
  499. if (count == 0) {
  500. // If read returns zero, it means the filedesc is EOF. Set whichever
  501. // one it was to closed and then use the other _read_into method.
  502. if (file_desc == std_out_pipe.in()) {
  503. std_out_pipe.close_in();
  504. } else {
  505. std_err_pipe.close_in();
  506. }
  507. return _read_into(buffer, length, seconds);
  508. }
  509. // Data was read, so return info on which stream it came from.
  510. return {
  511. (file_desc == std_out_pipe.in() ? ReadResult::StdOut
  512. : ReadResult::StdErr),
  513. count
  514. };
  515. }
  516. IndependentStdErrAndStdOut::ReadResult IndependentStdErrAndStdOut::_read_into(
  517. char * buffer, const size_t length, const optional<double> seconds)
  518. {
  519. int filedesc;
  520. ReadResult::FileIndex index;
  521. if (std_out_pipe.in_is_open()) {
  522. filedesc = std_out_pipe.in();
  523. index = ReadResult::FileIndex::StdOut;
  524. } else {
  525. filedesc = std_err_pipe.in();
  526. index = ReadResult::FileIndex::StdErr;
  527. }
  528. NOVA_LOG_TRACE("read_into with timeout=%f", !seconds ? 0.0 : seconds.get());
  529. if (!ready(filedesc, seconds)) {
  530. NOVA_LOG_TRACE("ready returned false, returning zero from read_into");
  531. return { ReadResult::TimeOut, 0 };
  532. }
  533. size_t count = io::read_with_throw(filedesc, buffer, length);
  534. if (count == 0) {
  535. NOVA_LOG_TRACE("read returned 0, EOF");
  536. draining = true; // Avoid re-draining.
  537. wait_forever_for_exit();
  538. return { ReadResult::Eof, 0 };
  539. }
  540. return { index, count };
  541. }
  542. /**---------------------------------------------------------------------------
  543. *- StdIn
  544. *---------------------------------------------------------------------------*/
  545. StdIn::StdIn()
  546. : std_in_pipe()
  547. {
  548. add_io_handler(this);
  549. }
  550. StdIn::~StdIn() {
  551. }
  552. void StdIn::set_eof_actions() {
  553. std_in_pipe.close_out();
  554. }
  555. void StdIn::pre_spawn_stdin_actions(SpawnFileActions & file_actions) {
  556. file_actions.add_dup_to(std_in_pipe.in(), STDIN_FILENO);
  557. file_actions.add_close(std_in_pipe.out());
  558. }
  559. void StdIn::post_spawn_actions() {
  560. std_in_pipe.close_in();
  561. }
  562. void StdIn::write(const char * msg) {
  563. const size_t maxlen = 2048;
  564. size_t length = (size_t) strnlen(msg, maxlen);
  565. if (length == maxlen) {
  566. NOVA_LOG_ERROR("String was not null terminated.");
  567. throw ProcessException(ProcessException::GENERAL);
  568. }
  569. this->write(msg, length);
  570. }
  571. void StdIn::write(const char * msg, size_t length) {
  572. //::write(std_in_fd[0], msg, length);
  573. NOVA_LOG_TRACE("Writing msg with %d bytes.", length);
  574. ssize_t count = ::write(this->std_in_pipe.out(), msg, length);
  575. if (count < 0) {
  576. NOVA_LOG_ERROR("write failed. errno = %d", errno);
  577. throw ProcessException(ProcessException::GENERAL);
  578. } else if (count != ((ssize_t)length)) {
  579. NOVA_LOG_ERROR("Did not write our message! errno = %d", errno);
  580. throw ProcessException(ProcessException::GENERAL);
  581. }
  582. }
  583. /**---------------------------------------------------------------------------
  584. *- StdErr
  585. *---------------------------------------------------------------------------*/
  586. StdErrToFile::StdErrToFile() {
  587. }
  588. StdErrToFile::~StdErrToFile() {
  589. }
  590. void StdErrToFile::post_spawn_actions() {
  591. ::close(file_descriptor);
  592. }
  593. void StdErrToFile::pre_spawn_stderr_actions(SpawnFileActions & file_actions) {
  594. file_descriptor = open(log_file_name(), O_WRONLY | O_APPEND | O_CREAT);
  595. file_actions.add_dup_to(file_descriptor, STDERR_FILENO);
  596. }
  597. /**---------------------------------------------------------------------------
  598. *- StdErrAndStdOut
  599. *---------------------------------------------------------------------------*/
  600. StdErrAndStdOut::StdErrAndStdOut()
  601. : draining(false),
  602. std_out_pipe()
  603. {
  604. ::fcntl(std_out_pipe.in(), F_SETFL, O_NONBLOCK);
  605. add_io_handler(this);
  606. }
  607. StdErrAndStdOut::~StdErrAndStdOut()
  608. {
  609. }
  610. void StdErrAndStdOut::drain_io(optional<double> seconds) {
  611. if (draining) {
  612. return;
  613. }
  614. NOVA_LOG_TRACE("Draining STDOUT / STDERR...");
  615. draining = true;
  616. char buffer[1024];
  617. size_t count;
  618. std::auto_ptr<Timer> timer;
  619. if (seconds) {
  620. timer.reset(new Timer(seconds.get()));
  621. }
  622. while (0 != (count = read_into(buffer, sizeof(buffer) - 1, seconds))) {
  623. NOVA_LOG_TRACE("Draining again! %d", count);
  624. };
  625. }
  626. void StdErrAndStdOut::pre_spawn_stderr_actions(SpawnFileActions & file_actions) {
  627. NOVA_LOG_TRACE("Opening up stderr pipes.");
  628. file_actions.add_dup_to(std_out_pipe.out(), STDERR_FILENO);
  629. }
  630. void StdErrAndStdOut::pre_spawn_stdout_actions(SpawnFileActions & file_actions) {
  631. NOVA_LOG_TRACE("Opening up stdout pipes.");
  632. file_actions.add_dup_to(std_out_pipe.out(), STDOUT_FILENO);
  633. file_actions.add_close(std_out_pipe.in());
  634. }
  635. void StdErrAndStdOut::post_spawn_actions() {
  636. std_out_pipe.close_out();
  637. NOVA_LOG_TRACE("Closing the out side of the stderr/stdout pipe.");
  638. }
  639. size_t StdErrAndStdOut::read_into(stringstream & std_out,
  640. const optional<double> seconds) {
  641. char buf[BUFFER_SIZE];
  642. for (size_t i = 0; i < BUFFER_SIZE; ++i)
  643. {
  644. buf[i] = '~';
  645. }
  646. size_t count = read_into(buf, BUFFER_SIZE-1, seconds);
  647. buf[count] = 0; // Have to do this or Valgrind fails.
  648. std_out.write(buf, count);
  649. NOVA_LOG_TRACE("buffer output:%s", buf);
  650. NOVA_LOG_TRACE("count = %d, SO FAR %d", count, std_out.str().length());
  651. return count;
  652. }
  653. size_t StdErrAndStdOut::read_into(char * buffer, const size_t length,
  654. const optional<double> seconds) {
  655. NOVA_LOG_TRACE("read_into with timeout=%f", !seconds ? 0.0 : seconds.get());
  656. if (!std_out_pipe.in_is_open()) {
  657. throw ProcessException(ProcessException::PROGRAM_FINISHED);
  658. }
  659. if (!ready(this->std_out_pipe.in(), seconds)) {
  660. NOVA_LOG_TRACE("ready returned false, returning zero from read_into");
  661. return 0;
  662. }
  663. size_t count = io::read_with_throw(this->std_out_pipe.in(), buffer, length);
  664. if (count == 0) {
  665. NOVA_LOG_TRACE("read returned 0, EOF");
  666. draining = true; // Avoid re-draining.
  667. wait_forever_for_exit();
  668. return 0; // eof
  669. }
  670. return (size_t) count;
  671. }
  672. void StdErrAndStdOut::read_into_until_exit(stringstream & out,
  673. double seconds) {
  674. NOVA_LOG_TRACE("wait_for_eof, timeout=%f", seconds);
  675. while(read_into(out, optional<double>(seconds)));
  676. if (std_out_pipe.in_is_open()) {
  677. NOVA_LOG_ERROR("Something went wrong, EOF not reached! Time out=%f",
  678. seconds);
  679. throw TimeOutException();
  680. }
  681. }
  682. size_t StdErrAndStdOut::read_until_pause(stringstream & std_out,
  683. const double time_out) {
  684. if (!std_out_pipe.in_is_open()) {
  685. throw ProcessException(ProcessException::PROGRAM_FINISHED);
  686. }
  687. size_t bytes_read = 0;
  688. size_t count;
  689. while(std_out_pipe.in_is_open() && (count = read_into(std_out, time_out)) > 0) {
  690. bytes_read += count;
  691. }
  692. return bytes_read;
  693. }
  694. void StdErrAndStdOut::set_eof_actions() {
  695. std_out_pipe.close_in();
  696. NOVA_LOG_TRACE("Closing in side of the stderr/stdout pipe.");
  697. }
  698. /**---------------------------------------------------------------------------
  699. *- StdOutOnly
  700. *---------------------------------------------------------------------------*/
  701. void StdOutOnly::pre_spawn_stderr_actions(SpawnFileActions & file_actions) {
  702. file_actions.add_close(STDERR_FILENO);
  703. }
  704. } } // end nova::process namespace